From ba670673916c6bac61b756778b73dd6fee2b46ad Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Tue, 19 May 2026 20:02:36 +0100 Subject: [PATCH 01/25] Add OpenSwarm product profile env --- .github/workflows/build-tui.yml | 5 +++++ bin/openswarm | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index dffd7cb..1fda102 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -152,6 +152,11 @@ jobs: AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py,agency.py + AGENTSWARM_PRODUCT_LOCK_MODEL_SELECTION: "true" + AGENTSWARM_PRODUCT_ADDONS_SETUP_FLAG_ENV: OPENSWARM_ONBOARD_FLAG + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' + AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' + AGENTSWARM_PRODUCT_WORDMARK_LINES: '[" ██████╗ ██████╗ ███████╗███╗ ██╗███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: ${{ needs.prepare.outputs.release_version }} run: bun run script/build.ts --single --skip-install diff --git a/bin/openswarm b/bin/openswarm index f971f9b..2d0a97b 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -11,6 +11,31 @@ const path = require('path') const scriptPath = fs.realpathSync(__filename) const packageDir = path.dirname(path.dirname(scriptPath)) const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) +const productTuiLogoLeft = [ + ' ██████╗ ██████╗ ███████╗███╗ ██╗', + '██╔═══██╗██╔══██╗██╔════╝████╗ ██║', + '██║ ██║██████╔╝█████╗ ██╔██╗ ██║', + '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║', + '╚██████╔╝██║ ███████╗██║ ╚████║', + ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝', +] +const productTuiLogoRight = [ + '', + '███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗', + '██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║', + '███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║', + '╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║', + '███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║', + '╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', +] +const productWordmarkLines = [ + ' ██████╗ ██████╗ ███████╗███╗ ██╗███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗', + '██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║', + '██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║', + '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║', + '╚██████╔╝██║ ███████╗██║ ╚████║███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║', + ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', +] const downstreamEnv = { AGENTSWARM_PRODUCT_DISPLAY_NAME: 'OpenSwarm', AGENTSWARM_PRODUCT_COMMAND: 'openswarm', @@ -23,6 +48,11 @@ const downstreamEnv = { AGENTSWARM_PRODUCT_STARTER_REPO: 'VRSEN/OpenSwarm', AGENTSWARM_PRODUCT_STARTER_FOLDER: 'openswarm', AGENTSWARM_PRODUCT_ENTRY_FILES: 'swarm.py,agency.py', + AGENTSWARM_PRODUCT_LOCK_MODEL_SELECTION: 'true', + AGENTSWARM_PRODUCT_ADDONS_SETUP_FLAG_ENV: 'OPENSWARM_ONBOARD_FLAG', + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: JSON.stringify(productTuiLogoLeft), + AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: JSON.stringify(productTuiLogoRight), + AGENTSWARM_PRODUCT_WORDMARK_LINES: JSON.stringify(productWordmarkLines), AGENTSWARM_PRODUCT_VERSION: packageJson.version, } From 5d064bb0472c070a3196a312a602e5293851a5ee Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 22 May 2026 03:02:12 +0100 Subject: [PATCH 02/25] Prepare OpenSwarm local release parity - use the final AgentSwarm product hook for skipping post-auth model selection - build OpenSwarm TUI assets from AgentSwarm v1.4.35 - let smoke tests install local AgentSwarm code and local OpenSwarm TUI binaries - add installed-path smoke proof for the /models command --- .github/workflows/build-tui.yml | 5 +- bin/openswarm | 3 +- scripts/smoke-run-mode.py | 138 ++++++++++++++++++++++++++++++-- 3 files changed, 135 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index 1fda102..eea93b9 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -17,7 +17,7 @@ permissions: contents: read env: - AGENTSWARM_CLI_VERSION: 1.4.34 + AGENTSWARM_CLI_VERSION: 1.4.35 jobs: prepare: @@ -152,8 +152,7 @@ jobs: AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py,agency.py - AGENTSWARM_PRODUCT_LOCK_MODEL_SELECTION: "true" - AGENTSWARM_PRODUCT_ADDONS_SETUP_FLAG_ENV: OPENSWARM_ONBOARD_FLAG + AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_WORDMARK_LINES: '[" ██████╗ ██████╗ ███████╗███╗ ██╗███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' diff --git a/bin/openswarm b/bin/openswarm index 2d0a97b..7b67b90 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -48,8 +48,7 @@ const downstreamEnv = { AGENTSWARM_PRODUCT_STARTER_REPO: 'VRSEN/OpenSwarm', AGENTSWARM_PRODUCT_STARTER_FOLDER: 'openswarm', AGENTSWARM_PRODUCT_ENTRY_FILES: 'swarm.py,agency.py', - AGENTSWARM_PRODUCT_LOCK_MODEL_SELECTION: 'true', - AGENTSWARM_PRODUCT_ADDONS_SETUP_FLAG_ENV: 'OPENSWARM_ONBOARD_FLAG', + AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: 'true', AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: JSON.stringify(productTuiLogoLeft), AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: JSON.stringify(productTuiLogoRight), AGENTSWARM_PRODUCT_WORDMARK_LINES: JSON.stringify(productWordmarkLines), diff --git a/scripts/smoke-run-mode.py b/scripts/smoke-run-mode.py index ba9f6cc..b0372a7 100644 --- a/scripts/smoke-run-mode.py +++ b/scripts/smoke-run-mode.py @@ -7,6 +7,7 @@ import json import os import pathlib +import platform import pty import re import select @@ -48,7 +49,71 @@ def latest_pack_stdout_line(stdout: str) -> str: raise RuntimeError("npm pack did not print a tarball name") -def install_package(repo: pathlib.Path, root: pathlib.Path, source: str, npm_spec: str, env: dict[str, str]) -> pathlib.Path: +def resolve_local_package(value: str) -> pathlib.Path: + package = pathlib.Path(value).expanduser() + if not package.is_absolute(): + package = pathlib.Path.cwd() / package + package = package.resolve() + if not package.exists(): + raise RuntimeError(f"local AgentSwarm package does not exist at {package}") + return package + + +def resolve_models_fixture(package: pathlib.Path | None) -> pathlib.Path | None: + if not package or not package.is_dir(): + return None + fixture = package / "test" / "tool" / "fixtures" / "models-api.json" + return fixture if fixture.exists() else None + + +def resolve_local_binary(value: str) -> pathlib.Path: + binary = pathlib.Path(value).expanduser() + if not binary.is_absolute(): + binary = pathlib.Path.cwd() / binary + binary = binary.resolve() + if not binary.exists(): + raise RuntimeError(f"local OpenSwarm TUI binary does not exist at {binary}") + if not binary.is_file(): + raise RuntimeError(f"local OpenSwarm TUI binary is not a file at {binary}") + return binary + + +def platform_asset_name() -> str: + machine = platform.machine().lower() + if machine in {"arm64", "aarch64"}: + arch = "arm64" + elif machine in {"x86_64", "amd64"}: + arch = "x64" + else: + arch = None + + if sys.platform == "darwin" and arch: + return f"agentswarm-darwin-{arch}" + if sys.platform.startswith("linux") and arch == "x64": + return "agentswarm-linux-x64" + if sys.platform == "win32" and arch == "x64": + return "agentswarm-windows-x64.exe" + raise RuntimeError( + f"unsupported OpenSwarm TUI asset platform: {sys.platform} {platform.machine()}" + ) + + +def install_openswarm_tui_binary(package_dir: pathlib.Path, binary: pathlib.Path) -> pathlib.Path: + target = package_dir / platform_asset_name() + shutil.copy2(binary, target) + if os.name != "nt": + target.chmod(target.stat().st_mode | 0o111) + return target + + +def install_package( + repo: pathlib.Path, + root: pathlib.Path, + source: str, + npm_spec: str, + agentswarm_path: pathlib.Path | None, + env: dict[str, str], +) -> pathlib.Path: run(["npm", "init", "-y"], cwd=root, env=env) if source == "local": packed = run(["npm", "pack"], cwd=repo, env=env) @@ -60,6 +125,9 @@ def install_package(repo: pathlib.Path, root: pathlib.Path, source: str, npm_spe else: run(["npm", "install", npm_spec], cwd=root, env=env) + if agentswarm_path: + run(["npm", "install", str(agentswarm_path)], cwd=root, env=env) + launcher = root / "node_modules" / ".bin" / "openswarm" if not launcher.exists(): raise RuntimeError(f"openswarm launcher was not installed at {launcher}") @@ -162,6 +230,11 @@ def run_tui_smoke( sent_agents_command = False verified_agents = False closed_agents_at: float | None = None + sent_models_command_menu = False + sent_models_filter = False + models_menu_start = 0 + verified_models = False + closed_models_at: float | None = None sent_prompt = False saw_expected = False deadline = time.monotonic() + timeout @@ -206,12 +279,43 @@ def run_tui_smoke( verified_agents = True closed_agents_at = closed_agents_at or time.monotonic() + agents_ready = check not in {"agents", "all"} or verified_agents + if ( + check in {"models", "all"} + and agents_ready + and not sent_models_command_menu + and run_mode_ready + and (closed_agents_at is None or time.monotonic() - closed_agents_at > 0.5) + ): + models_menu_start = len(plain) + write(master_fd, "\x10") + sent_models_command_menu = True + + if sent_models_command_menu and not verified_models: + models_menu = compact(plain[models_menu_start:]).lower() + if "/models" in models_menu or "switchmodel" in models_menu: + verified_models = True + write(master_fd, "\x1b") + closed_models_at = time.monotonic() + if check == "models": + return plain + elif "commands" in models_menu and not sent_models_filter: + write(master_fd, "model") + sent_models_filter = True + + if check == "prompt": + verified_models = True + closed_models_at = closed_models_at or closed_agents_at or time.monotonic() + + models_ready = check not in {"models", "all"} or verified_models + closed_picker_at = closed_models_at or closed_agents_at if ( check in {"prompt", "all"} and verified_agents + and models_ready and not sent_prompt - and closed_agents_at is not None - and time.monotonic() - closed_agents_at > 0.5 + and closed_picker_at is not None + and time.monotonic() - closed_picker_at > 0.5 ): write(master_fd, prompt + "\r") sent_prompt = True @@ -229,7 +333,9 @@ def run_tui_smoke( raise RuntimeError( "OpenSwarm Run-mode smoke test did not reach the expected response. " f"sent_confirm={sent_confirm} sent_agents_command={sent_agents_command} " - f"verified_agents={verified_agents} sent_prompt={sent_prompt} saw_expected={saw_expected} log={log_path}" + f"verified_agents={verified_agents} sent_models_command_menu={sent_models_command_menu} " + f"sent_models_filter={sent_models_filter} " + f"verified_models={verified_models} sent_prompt={sent_prompt} saw_expected={saw_expected} log={log_path}" ) @@ -237,7 +343,15 @@ def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--source", choices=["local", "npm"], default="local") parser.add_argument("--npm-spec") - parser.add_argument("--check", choices=["all", "agents", "prompt"], default="all") + parser.add_argument( + "--agentswarm-package", + help="Local @vrsen/agentswarm tarball or package directory to install after OpenSwarm for smoke proof.", + ) + parser.add_argument( + "--openswarm-tui-binary", + help="Local OpenSwarm-branded AgentSwarm TUI binary to copy into the installed @vrsen/openswarm package.", + ) + parser.add_argument("--check", choices=["all", "agents", "models", "prompt"], default="all") parser.add_argument("--prompt", default=DEFAULT_PROMPT) parser.add_argument("--expect", default=DEFAULT_EXPECT) parser.add_argument("--timeout", type=int, default=1200) @@ -247,6 +361,9 @@ def main() -> int: repo = pathlib.Path(__file__).resolve().parents[1] package_json = json.loads((repo / "package.json").read_text(encoding="utf-8")) npm_spec = args.npm_spec or f"{package_json['name']}@{package_json['version']}" + agentswarm_path = resolve_local_package(args.agentswarm_package) if args.agentswarm_package else None + openswarm_tui_binary = resolve_local_binary(args.openswarm_tui_binary) if args.openswarm_tui_binary else None + models_fixture = resolve_models_fixture(agentswarm_path) api_key = os.environ.get("OPENAI_API_KEY") if not api_key and args.check == "prompt" and os.environ.get("GITHUB_ACTIONS") == "true": print("Skipped OpenSwarm live prompt smoke because OPENAI_API_KEY is not configured") @@ -260,23 +377,32 @@ def main() -> int: { "PYTHONUTF8": "1", "PYTHONIOENCODING": "utf-8", + "CI": "1", "TERM": "xterm-256color", "OPENCODE_AUTH_CONTENT": json.dumps({"openai": {"type": "api", "key": auth_key}}), "XDG_DATA_HOME": str(root / "xdg-data"), "XDG_CONFIG_HOME": str(root / "xdg-config"), "XDG_CACHE_HOME": str(root / "xdg-cache"), "XDG_STATE_HOME": str(root / "xdg-state"), + "OPENCODE_DISABLE_AUTOUPDATE": "true", + "OPENCODE_DISABLE_MODELS_FETCH": "true", } ) + if models_fixture: + env["OPENCODE_MODELS_PATH"] = str(models_fixture) try: - launcher = install_package(repo, root, args.source, npm_spec, env) + launcher = install_package(repo, root, args.source, npm_spec, agentswarm_path, env) package_dir = root / "node_modules" / "@vrsen" / "openswarm" + if openswarm_tui_binary: + install_openswarm_tui_binary(package_dir, openswarm_tui_binary) plain = run_tui_smoke(launcher, package_dir, root, env, args.check, args.prompt, args.expect, args.timeout) if "Agency Swarm Default" not in plain: raise RuntimeError("Smoke response was seen, but Agency Swarm Run mode was not detected") if args.check in {"agents", "all"}: print(f"OpenSwarm /agents smoke passed with {len(EXPECTED_SPECIALIST_AGENTS)} specialists visible") + if args.check in {"models", "all"}: + print("OpenSwarm /models smoke passed") if args.check in {"prompt", "all"}: print("OpenSwarm live prompt smoke passed") print(f"OpenSwarm smoke root package: {package_dir}") From b1d0f3242fe95c28acf5db53e74e85a944ba4f22 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 22 May 2026 19:07:08 +0100 Subject: [PATCH 03/25] Tighten OpenSwarm model smoke proof - Drive Ctrl+P through the Switch model command before checking the picker\n- Require model-picker content after Enter so command-list matches cannot pass --- scripts/smoke-run-mode.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/scripts/smoke-run-mode.py b/scripts/smoke-run-mode.py index b0372a7..4e7b6f1 100644 --- a/scripts/smoke-run-mode.py +++ b/scripts/smoke-run-mode.py @@ -232,7 +232,10 @@ def run_tui_smoke( closed_agents_at: float | None = None sent_models_command_menu = False sent_models_filter = False + selected_models_command = False + models_filter_at: float | None = None models_menu_start = 0 + models_picker_start = 0 verified_models = False closed_models_at: float | None = None sent_prompt = False @@ -291,17 +294,32 @@ def run_tui_smoke( write(master_fd, "\x10") sent_models_command_menu = True - if sent_models_command_menu and not verified_models: + if sent_models_command_menu and not selected_models_command: models_menu = compact(plain[models_menu_start:]).lower() - if "/models" in models_menu or "switchmodel" in models_menu: + if "commands" in models_menu and not sent_models_filter: + write(master_fd, "model") + sent_models_filter = True + models_filter_at = time.monotonic() + elif ( + sent_models_filter + and models_filter_at is not None + and time.monotonic() - models_filter_at > 0.5 + and ("/models" in models_menu or "switchmodel" in models_menu) + ): + models_picker_start = len(plain) + write(master_fd, "\r") + selected_models_command = True + + if selected_models_command and not verified_models: + models_picker = compact(plain[models_picker_start:]).lower() + if "selectmodel" in models_picker and ( + "agencyswarmdefault" in models_picker or "manageproviderauth" in models_picker + ): verified_models = True write(master_fd, "\x1b") closed_models_at = time.monotonic() if check == "models": return plain - elif "commands" in models_menu and not sent_models_filter: - write(master_fd, "model") - sent_models_filter = True if check == "prompt": verified_models = True @@ -334,7 +352,7 @@ def run_tui_smoke( "OpenSwarm Run-mode smoke test did not reach the expected response. " f"sent_confirm={sent_confirm} sent_agents_command={sent_agents_command} " f"verified_agents={verified_agents} sent_models_command_menu={sent_models_command_menu} " - f"sent_models_filter={sent_models_filter} " + f"sent_models_filter={sent_models_filter} selected_models_command={selected_models_command} " f"verified_models={verified_models} sent_prompt={sent_prompt} saw_expected={saw_expected} log={log_path}" ) From 33b3f487ed4ea312f035b75bfbc5189221bfb529 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 22 May 2026 19:16:19 +0100 Subject: [PATCH 04/25] Fail when OpenSwarm TUI is unavailable - Avoid falling back to stale packaged AgentSwarm without product flags\n- Keep custom TUI path required for OpenSwarm launcher parity --- bin/openswarm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/openswarm b/bin/openswarm index 7b67b90..3a06aa6 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -200,10 +200,9 @@ async function ensureCustomBinary() { return resolved } catch (error) { fs.rmSync(binaryPath, { force: true }) - process.stderr.write( - `openswarm: custom OpenSwarm TUI unavailable (${error instanceof Error ? error.message : String(error)}); falling back to packaged AgentSwarm UI.\n`, + throw new Error( + `custom OpenSwarm TUI unavailable (${error instanceof Error ? error.message : String(error)}). Reinstall @vrsen/openswarm or set OPENSWARM_TUI_URL to a valid binary.`, ) - return null } } From 5793b8ee20cd515b354c5b450699458891fcb33b Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 22 May 2026 19:58:32 +0100 Subject: [PATCH 05/25] Align OpenSwarm smoke proof with local TUI - Build the local OpenSwarm TUI for live smoke checks\n- Expect missing custom TUI binaries to fail closed\n- Prove /models through slash autocomplete and picker behavior --- .github/workflows/live-run-mode-smoke.yml | 44 +++++++++++++- .github/workflows/test-mac.yml | 55 ++++++------------ scripts/smoke-run-mode.py | 71 ++++++++++++++++------- 3 files changed, 111 insertions(+), 59 deletions(-) diff --git a/.github/workflows/live-run-mode-smoke.yml b/.github/workflows/live-run-mode-smoke.yml index 8ab5d09..ab1fc70 100644 --- a/.github/workflows/live-run-mode-smoke.yml +++ b/.github/workflows/live-run-mode-smoke.yml @@ -29,11 +29,51 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.12' + - uses: actions/checkout@v4 + with: + repository: VRSEN/agentswarm-cli + ref: v1.4.35 + path: agentswarm-cli + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.13" + - name: Resolve OpenSwarm package version + id: openswarm + run: node -e "console.log('version=' + require('./package.json').version)" >> "$GITHUB_OUTPUT" + - name: Build local OpenSwarm TUI + shell: bash + working-directory: agentswarm-cli + env: + OPENCODE_CHANNEL: rc + AGENTSWARM_PRODUCT_DISPLAY_NAME: OpenSwarm + AGENTSWARM_PRODUCT_COMMAND: openswarm + AGENTSWARM_PRODUCT_PACKAGE_NAME: "@vrsen/openswarm" + AGENTSWARM_PRODUCT_LAUNCHER_PACKAGE_NAME: "@vrsen/openswarm" + AGENTSWARM_PRODUCT_RELEASE_REPO: VRSEN/OpenSwarm + AGENTSWARM_PRODUCT_DOCS_URL: https://github.com/VRSEN/OpenSwarm + AGENTSWARM_PRODUCT_ISSUE_URL: https://github.com/VRSEN/OpenSwarm/issues/new?template=bug-report.yml + AGENTSWARM_PRODUCT_MDNS_DOMAIN: openswarm.local + AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm + AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm + AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py,agency.py + AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" + AGENTSWARM_PRODUCT_VERSION: ${{ steps.openswarm.outputs.version }} + run: | + bun install + cd packages/opencode + bun run script/build.ts --single --skip-install + binary="$(find dist -path '*/bin/agentswarm' -type f -perm -111 | head -n 1)" + if [ -z "$binary" ]; then + echo "FAILED: built agentswarm binary not found under agentswarm-cli/packages/opencode/dist" >&2 + find dist -maxdepth 4 -type f >&2 + exit 1 + fi + echo "OPENSWARM_TUI_BINARY=$PWD/$binary" >> "$GITHUB_ENV" - name: Verify OpenSwarm agent roster through TUI env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: python3 scripts/smoke-run-mode.py --source local --check agents + run: python3 scripts/smoke-run-mode.py --source local --openswarm-tui-binary "$OPENSWARM_TUI_BINARY" --check agents - name: Verify live Agency Swarm Run-mode prompt env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: python3 scripts/smoke-run-mode.py --source local --check prompt + run: python3 scripts/smoke-run-mode.py --source local --openswarm-tui-binary "$OPENSWARM_TUI_BINARY" --check prompt diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 209d8d1..8a68d17 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v4 with: repository: VRSEN/agentswarm-cli - ref: v1.4.34 + ref: v1.4.35 path: agentswarm-cli - uses: oven-sh/setup-bun@v2 with: @@ -58,7 +58,6 @@ jobs: import os import pathlib import platform - import re import subprocess import sys import tempfile @@ -134,12 +133,12 @@ jobs: else: listing = None - failure_runs = [] + missing_runs = [] invalid_runs = [] success_runs = [] if binary is not None: - failure_env = {**env, "OPENSWARM_TUI_URL": f"{base_url}/missing/{binary_name}"} - failure_runs = [run_version(binary, root, failure_env) for _ in range(2)] + missing_env = {**env, "OPENSWARM_TUI_URL": f"{base_url}/missing/{binary_name}"} + missing_runs = [run_version(binary, root, missing_env) for _ in range(2)] invalid_binary = release_dir / binary_name invalid_binary.write_text("#!/bin/sh\nkill -9 $$\n", encoding="utf-8") @@ -174,8 +173,8 @@ jobs: "" if listing is None else listing.stderr, ] + [ - f"=== fallback run {idx} stdout ===\n{run.stdout}\n=== fallback run {idx} stderr ===\n{run.stderr}" - for idx, run in enumerate(failure_runs, start=1) + f"=== missing custom TUI run {idx} stdout ===\n{run.stdout}\n=== missing custom TUI run {idx} stderr ===\n{run.stderr}" + for idx, run in enumerate(missing_runs, start=1) ] + [ f"=== invalid run {idx} stdout ===\n{run.stdout}\n=== invalid run {idx} stderr ===\n{run.stderr}" @@ -196,9 +195,13 @@ jobs: print("FAILED: expected launcher not found after npm install") sys.exit(1) - for idx, run in enumerate(failure_runs, start=1): - if run.returncode != 0: - print(f"FAILED: fallback run {idx} exited with {run.returncode}") + for idx, run in enumerate(missing_runs, start=1): + if run.returncode == 0: + print(f"FAILED: missing custom TUI run {idx} unexpectedly succeeded") + sys.exit(1) + stderr = run.stderr.lower() + if "custom openswarm tui unavailable" not in stderr or "openswarm_tui_url" not in stderr: + print(f"FAILED: missing custom TUI run {idx} did not print actionable custom TUI unavailable message") sys.exit(1) for idx, run in enumerate(success_runs, start=1): @@ -207,8 +210,12 @@ jobs: sys.exit(1) for idx, run in enumerate(invalid_runs, start=1): - if run.returncode != 0: - print(f"FAILED: invalid run {idx} exited with {run.returncode}") + if run.returncode == 0: + print(f"FAILED: invalid run {idx} unexpectedly succeeded") + sys.exit(1) + stderr = run.stderr.lower() + if "custom openswarm tui unavailable" not in stderr or "openswarm_tui_url" not in stderr: + print(f"FAILED: invalid run {idx} did not print actionable custom TUI unavailable message") sys.exit(1) banned = [ @@ -223,30 +230,6 @@ jobs: print(f"FAILED: found unexpected output: {needle}") sys.exit(1) - fallback_version = next( - ( - line.strip() - for line in reversed(failure_runs[-1].stdout.splitlines()) - if line.strip() - ), - "", - ) - if not re.fullmatch(r"\d+\.\d+\.\d+", fallback_version): - print(f"FAILED: unexpected fallback version output: {fallback_version!r}") - sys.exit(1) - - invalid_version = next( - ( - line.strip() - for line in reversed(invalid_runs[-1].stdout.splitlines()) - if line.strip() - ), - "", - ) - if not re.fullmatch(r"\d+\.\d+\.\d+", invalid_version): - print(f"FAILED: unexpected invalid-binary fallback version output: {invalid_version!r}") - sys.exit(1) - custom_version = next( ( line.strip() diff --git a/scripts/smoke-run-mode.py b/scripts/smoke-run-mode.py index 4e7b6f1..60b58da 100644 --- a/scripts/smoke-run-mode.py +++ b/scripts/smoke-run-mode.py @@ -230,11 +230,17 @@ def run_tui_smoke( sent_agents_command = False verified_agents = False closed_agents_at: float | None = None - sent_models_command_menu = False - sent_models_filter = False + sent_models_slash = False + saw_models_slash = False + cleared_models_probe = False + sent_models_direct = False + saw_models_direct = False selected_models_command = False - models_filter_at: float | None = None - models_menu_start = 0 + models_slash_at: float | None = None + models_clear_at: float | None = None + models_direct_at: float | None = None + models_slash_start = 0 + models_direct_start = 0 models_picker_start = 0 verified_models = False closed_models_at: float | None = None @@ -286,25 +292,46 @@ def run_tui_smoke( if ( check in {"models", "all"} and agents_ready - and not sent_models_command_menu + and not sent_models_slash and run_mode_ready and (closed_agents_at is None or time.monotonic() - closed_agents_at > 0.5) ): - models_menu_start = len(plain) - write(master_fd, "\x10") - sent_models_command_menu = True - - if sent_models_command_menu and not selected_models_command: - models_menu = compact(plain[models_menu_start:]).lower() - if "commands" in models_menu and not sent_models_filter: - write(master_fd, "model") - sent_models_filter = True - models_filter_at = time.monotonic() + models_slash_start = len(plain) + write(master_fd, "/") + sent_models_slash = True + models_slash_at = time.monotonic() + + if sent_models_slash and not selected_models_command: + models_slash = compact(plain[models_slash_start:]).lower() + if not saw_models_slash and "/models" in models_slash: + saw_models_slash = True + if ( + saw_models_slash + and not cleared_models_probe + and models_slash_at is not None + and time.monotonic() - models_slash_at > 0.5 + ): + write(master_fd, "\x7f") + cleared_models_probe = True + models_clear_at = time.monotonic() + elif ( + cleared_models_probe + and not sent_models_direct + and models_clear_at is not None + and time.monotonic() - models_clear_at > 0.2 + ): + models_direct_start = len(plain) + write(master_fd, "/models") + sent_models_direct = True + models_direct_at = time.monotonic() + elif sent_models_direct and not saw_models_direct: + models_direct = compact(plain[models_direct_start:]).lower() + if "/models" in models_direct and "switchmodel" in models_direct: + saw_models_direct = True elif ( - sent_models_filter - and models_filter_at is not None - and time.monotonic() - models_filter_at > 0.5 - and ("/models" in models_menu or "switchmodel" in models_menu) + saw_models_direct + and models_direct_at is not None + and time.monotonic() - models_direct_at > 0.5 ): models_picker_start = len(plain) write(master_fd, "\r") @@ -351,8 +378,10 @@ def run_tui_smoke( raise RuntimeError( "OpenSwarm Run-mode smoke test did not reach the expected response. " f"sent_confirm={sent_confirm} sent_agents_command={sent_agents_command} " - f"verified_agents={verified_agents} sent_models_command_menu={sent_models_command_menu} " - f"sent_models_filter={sent_models_filter} selected_models_command={selected_models_command} " + f"verified_agents={verified_agents} sent_models_slash={sent_models_slash} " + f"saw_models_slash={saw_models_slash} cleared_models_probe={cleared_models_probe} " + f"sent_models_direct={sent_models_direct} saw_models_direct={saw_models_direct} " + f"selected_models_command={selected_models_command} " f"verified_models={verified_models} sent_prompt={sent_prompt} saw_expected={saw_expected} log={log_path}" ) From 8d1f6b7d74416e7f646801281175f51aeb1f6ec9 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 22 May 2026 22:26:34 +0100 Subject: [PATCH 06/25] Fix OpenSwarm local launch parity - restrict OpenSwarm project detection to swarm.py - remove oversized custom TUI logo injection - prove generic agency.py folders still launch the OpenSwarm swarm --- .github/workflows/build-tui.yml | 5 +-- .github/workflows/live-run-mode-smoke.yml | 2 +- .github/workflows/test-mac.yml | 2 +- bin/openswarm | 30 +----------------- scripts/smoke-run-mode.py | 37 +++++++++++++++++++++-- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index eea93b9..79c3072 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -151,11 +151,8 @@ jobs: AGENTSWARM_PRODUCT_MDNS_DOMAIN: openswarm.local AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm - AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py,agency.py + AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" - AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' - AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' - AGENTSWARM_PRODUCT_WORDMARK_LINES: '[" ██████╗ ██████╗ ███████╗███╗ ██╗███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: ${{ needs.prepare.outputs.release_version }} run: bun run script/build.ts --single --skip-install diff --git a/.github/workflows/live-run-mode-smoke.yml b/.github/workflows/live-run-mode-smoke.yml index ab1fc70..1debe9e 100644 --- a/.github/workflows/live-run-mode-smoke.yml +++ b/.github/workflows/live-run-mode-smoke.yml @@ -55,7 +55,7 @@ jobs: AGENTSWARM_PRODUCT_MDNS_DOMAIN: openswarm.local AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm - AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py,agency.py + AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" AGENTSWARM_PRODUCT_VERSION: ${{ steps.openswarm.outputs.version }} run: | diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 8a68d17..aa4352e 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -42,7 +42,7 @@ jobs: AGENTSWARM_PRODUCT_MDNS_DOMAIN: openswarm.local AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm - AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py,agency.py + AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py AGENTSWARM_PRODUCT_VERSION: 1.0.1-rc.4 run: | bun install diff --git a/bin/openswarm b/bin/openswarm index 3a06aa6..8559806 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -11,31 +11,6 @@ const path = require('path') const scriptPath = fs.realpathSync(__filename) const packageDir = path.dirname(path.dirname(scriptPath)) const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) -const productTuiLogoLeft = [ - ' ██████╗ ██████╗ ███████╗███╗ ██╗', - '██╔═══██╗██╔══██╗██╔════╝████╗ ██║', - '██║ ██║██████╔╝█████╗ ██╔██╗ ██║', - '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║', - '╚██████╔╝██║ ███████╗██║ ╚████║', - ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝', -] -const productTuiLogoRight = [ - '', - '███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗', - '██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║', - '███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║', - '╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║', - '███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║', - '╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', -] -const productWordmarkLines = [ - ' ██████╗ ██████╗ ███████╗███╗ ██╗███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗', - '██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║', - '██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║', - '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║', - '╚██████╔╝██║ ███████╗██║ ╚████║███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║', - ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', -] const downstreamEnv = { AGENTSWARM_PRODUCT_DISPLAY_NAME: 'OpenSwarm', AGENTSWARM_PRODUCT_COMMAND: 'openswarm', @@ -47,11 +22,8 @@ const downstreamEnv = { AGENTSWARM_PRODUCT_MDNS_DOMAIN: 'openswarm.local', AGENTSWARM_PRODUCT_STARTER_REPO: 'VRSEN/OpenSwarm', AGENTSWARM_PRODUCT_STARTER_FOLDER: 'openswarm', - AGENTSWARM_PRODUCT_ENTRY_FILES: 'swarm.py,agency.py', + AGENTSWARM_PRODUCT_ENTRY_FILES: 'swarm.py', AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: 'true', - AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: JSON.stringify(productTuiLogoLeft), - AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: JSON.stringify(productTuiLogoRight), - AGENTSWARM_PRODUCT_WORDMARK_LINES: JSON.stringify(productWordmarkLines), AGENTSWARM_PRODUCT_VERSION: packageJson.version, } diff --git a/scripts/smoke-run-mode.py b/scripts/smoke-run-mode.py index 60b58da..661609b 100644 --- a/scripts/smoke-run-mode.py +++ b/scripts/smoke-run-mode.py @@ -35,6 +35,7 @@ "Video Agent", "Image Agent", ] +EXPECTED_AGENT_COUNT = 1 + len(EXPECTED_SPECIALIST_AGENTS) def run(cmd: Sequence[str], *, cwd: pathlib.Path, env: dict[str, str]) -> subprocess.CompletedProcess[str]: @@ -106,6 +107,20 @@ def install_openswarm_tui_binary(package_dir: pathlib.Path, binary: pathlib.Path return target +def create_local_openswarm_project(package_dir: pathlib.Path, root: pathlib.Path) -> pathlib.Path: + target = root / "openswarm" + ignore = shutil.ignore_patterns( + ".git", + ".venv", + "__pycache__", + "node_modules", + "agentswarm-*", + "*.tgz", + ) + shutil.copytree(package_dir, target, ignore=ignore) + return target + + def install_package( repo: pathlib.Path, root: pathlib.Path, @@ -204,6 +219,7 @@ def set_window_size(fd: int, rows: int = 45, columns: int = 180) -> None: def run_tui_smoke( launcher: pathlib.Path, package_dir: pathlib.Path, + launcher_cwd: pathlib.Path, root: pathlib.Path, env: dict[str, str], check: str, @@ -215,7 +231,7 @@ def run_tui_smoke( set_window_size(slave_fd) process = subprocess.Popen( [str(launcher)], - cwd=package_dir, + cwd=launcher_cwd, env=env, stdin=slave_fd, stdout=slave_fd, @@ -443,11 +459,26 @@ def main() -> int: package_dir = root / "node_modules" / "@vrsen" / "openswarm" if openswarm_tui_binary: install_openswarm_tui_binary(package_dir, openswarm_tui_binary) - plain = run_tui_smoke(launcher, package_dir, root, env, args.check, args.prompt, args.expect, args.timeout) + generic_dir = root / "my-agency" + generic_dir.mkdir() + (generic_dir / "agency.py").write_text( + "\n".join( + [ + "from agency_swarm import Agency", + "", + "def create_agency(load_threads_callback=None):", + " return Agency(name='Generic Agency')", + "", + ] + ), + encoding="utf-8", + ) + create_local_openswarm_project(package_dir, generic_dir) + plain = run_tui_smoke(launcher, package_dir, generic_dir, root, env, args.check, args.prompt, args.expect, args.timeout) if "Agency Swarm Default" not in plain: raise RuntimeError("Smoke response was seen, but Agency Swarm Run mode was not detected") if args.check in {"agents", "all"}: - print(f"OpenSwarm /agents smoke passed with {len(EXPECTED_SPECIALIST_AGENTS)} specialists visible") + print(f"OpenSwarm /agents smoke passed with {EXPECTED_AGENT_COUNT} agents visible") if args.check in {"models", "all"}: print("OpenSwarm /models smoke passed") if args.check in {"prompt", "all"}: From 467451acf78a16705637ddcaf782ff10be07bdb6 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 22 May 2026 23:12:40 +0100 Subject: [PATCH 07/25] Restore OpenSwarm TUI logo - pass the OpenSwarm OPEN/SWARM logo through wrapper and build env - preserve swarm.py-only detection and visible model selection - refresh local QA proof for the OpenSwarm startup logo --- .github/workflows/build-tui.yml | 2 ++ .github/workflows/live-run-mode-smoke.yml | 2 ++ .github/workflows/test-mac.yml | 2 ++ bin/openswarm | 19 +++++++++++++++++ run_utils.py | 25 +++++++++++++++++++++++ 5 files changed, 50 insertions(+) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index 79c3072..51ce717 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -153,6 +153,8 @@ jobs: AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' + AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: ${{ needs.prepare.outputs.release_version }} run: bun run script/build.ts --single --skip-install diff --git a/.github/workflows/live-run-mode-smoke.yml b/.github/workflows/live-run-mode-smoke.yml index 1debe9e..d617b6d 100644 --- a/.github/workflows/live-run-mode-smoke.yml +++ b/.github/workflows/live-run-mode-smoke.yml @@ -57,6 +57,8 @@ jobs: AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' + AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: ${{ steps.openswarm.outputs.version }} run: | bun install diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index aa4352e..6ac1af8 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -43,6 +43,8 @@ jobs: AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' + AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: 1.0.1-rc.4 run: | bun install diff --git a/bin/openswarm b/bin/openswarm index 8559806..07ef80b 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -11,6 +11,23 @@ const path = require('path') const scriptPath = fs.realpathSync(__filename) const packageDir = path.dirname(path.dirname(scriptPath)) const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) +const productTuiLogoLeft = [ + ' ██████╗ ██████╗ ███████╗███╗ ██╗', + '██╔═══██╗██╔══██╗██╔════╝████╗ ██║', + '██║ ██║██████╔╝█████╗ ██╔██╗ ██║', + '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║', + '╚██████╔╝██║ ███████╗██║ ╚████║', + ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝', +] +const productTuiLogoRight = [ + '', + '███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗', + '██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║', + '███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║', + '╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║', + '███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║', + '╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', +] const downstreamEnv = { AGENTSWARM_PRODUCT_DISPLAY_NAME: 'OpenSwarm', AGENTSWARM_PRODUCT_COMMAND: 'openswarm', @@ -24,6 +41,8 @@ const downstreamEnv = { AGENTSWARM_PRODUCT_STARTER_FOLDER: 'openswarm', AGENTSWARM_PRODUCT_ENTRY_FILES: 'swarm.py', AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: 'true', + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: JSON.stringify(productTuiLogoLeft), + AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: JSON.stringify(productTuiLogoRight), AGENTSWARM_PRODUCT_VERSION: packageJson.version, } diff --git a/run_utils.py b/run_utils.py index 76813e9..fc7c4b3 100644 --- a/run_utils.py +++ b/run_utils.py @@ -5,6 +5,30 @@ import tempfile from pathlib import Path +_PRODUCT_TUI_LOGO_LEFT = ( + '[" ██████╗ ██████╗ ███████╗███╗ ██╗",' + '"██╔═══██╗██╔══██╗██╔════╝████╗ ██║",' + '"██║ ██║██████╔╝█████╗ ██╔██╗ ██║",' + '"██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║",' + '"╚██████╔╝██║ ███████╗██║ ╚████║",' + '" ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' +) +_PRODUCT_TUI_LOGO_RIGHT = ( + '["",' + '"███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗",' + '"██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║",' + '"███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║",' + '"╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║",' + '"███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║",' + '"╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' +) + + +def _configure_product_env() -> None: + os.environ.setdefault("AGENTSWARM_PRODUCT_TUI_LOGO_LEFT", _PRODUCT_TUI_LOGO_LEFT) + os.environ.setdefault("AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT", _PRODUCT_TUI_LOGO_RIGHT) + + def _resolve_bin_name() -> str: """Return the platform+arch-specific TUI binary filename.""" import platform @@ -237,6 +261,7 @@ def main() -> None: os.environ.setdefault("PYTHONUTF8", "1") os.environ.setdefault("PYTHONIOENCODING", "utf-8") + _configure_product_env() if not os.getenv("AGENTSWARM_BIN"): _repo = Path(__file__).resolve().parent From 7efbb1f83013f26c50110913b353d3f2f2897e64 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Fri, 22 May 2026 23:36:48 +0100 Subject: [PATCH 08/25] Restore OpenSwarm wordmark logo parity - add the missing OpenSwarm wordmark env for non-TTY and help output - align split TUI logo rows with the manual OpenSwarm release logo --- .github/workflows/build-tui.yml | 3 ++- .github/workflows/live-run-mode-smoke.yml | 3 ++- .github/workflows/test-mac.yml | 3 ++- bin/openswarm | 3 +++ run_utils.py | 13 ++++++++++++- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index 51ce717..714fed0 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -153,8 +153,9 @@ jobs: AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" - AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" "," ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' + AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: ${{ needs.prepare.outputs.release_version }} run: bun run script/build.ts --single --skip-install diff --git a/.github/workflows/live-run-mode-smoke.yml b/.github/workflows/live-run-mode-smoke.yml index d617b6d..e159100 100644 --- a/.github/workflows/live-run-mode-smoke.yml +++ b/.github/workflows/live-run-mode-smoke.yml @@ -57,8 +57,9 @@ jobs: AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: "true" - AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" "," ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' + AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: ${{ steps.openswarm.outputs.version }} run: | bun install diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 6ac1af8..03cd127 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -43,8 +43,9 @@ jobs: AGENTSWARM_PRODUCT_STARTER_REPO: VRSEN/OpenSwarm AGENTSWARM_PRODUCT_STARTER_FOLDER: openswarm AGENTSWARM_PRODUCT_ENTRY_FILES: swarm.py - AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' + AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" "," ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' + AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: 1.0.1-rc.4 run: | bun install diff --git a/bin/openswarm b/bin/openswarm index 07ef80b..1a4e474 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -12,6 +12,7 @@ const scriptPath = fs.realpathSync(__filename) const packageDir = path.dirname(path.dirname(scriptPath)) const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) const productTuiLogoLeft = [ + ' ', ' ██████╗ ██████╗ ███████╗███╗ ██╗', '██╔═══██╗██╔══██╗██╔════╝████╗ ██║', '██║ ██║██████╔╝█████╗ ██╔██╗ ██║', @@ -28,6 +29,7 @@ const productTuiLogoRight = [ '███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║', '╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', ] +const productWordmarkLines = productTuiLogoLeft.map((line, index) => `${line} ${productTuiLogoRight[index] ?? ''}`.trimEnd()) const downstreamEnv = { AGENTSWARM_PRODUCT_DISPLAY_NAME: 'OpenSwarm', AGENTSWARM_PRODUCT_COMMAND: 'openswarm', @@ -43,6 +45,7 @@ const downstreamEnv = { AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION: 'true', AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: JSON.stringify(productTuiLogoLeft), AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: JSON.stringify(productTuiLogoRight), + AGENTSWARM_PRODUCT_WORDMARK_LINES: JSON.stringify(productWordmarkLines), AGENTSWARM_PRODUCT_VERSION: packageJson.version, } diff --git a/run_utils.py b/run_utils.py index fc7c4b3..fb28e0e 100644 --- a/run_utils.py +++ b/run_utils.py @@ -6,7 +6,8 @@ from pathlib import Path _PRODUCT_TUI_LOGO_LEFT = ( - '[" ██████╗ ██████╗ ███████╗███╗ ██╗",' + '[" ",' + '" ██████╗ ██████╗ ███████╗███╗ ██╗",' '"██╔═══██╗██╔══██╗██╔════╝████╗ ██║",' '"██║ ██║██████╔╝█████╗ ██╔██╗ ██║",' '"██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║",' @@ -22,11 +23,21 @@ '"███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║",' '"╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' ) +_PRODUCT_WORDMARK_LINES = ( + '["",' + '" ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗",' + '"██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║",' + '"██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║",' + '"██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║",' + '"╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║",' + '" ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' +) def _configure_product_env() -> None: os.environ.setdefault("AGENTSWARM_PRODUCT_TUI_LOGO_LEFT", _PRODUCT_TUI_LOGO_LEFT) os.environ.setdefault("AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT", _PRODUCT_TUI_LOGO_RIGHT) + os.environ.setdefault("AGENTSWARM_PRODUCT_WORDMARK_LINES", _PRODUCT_WORDMARK_LINES) def _resolve_bin_name() -> str: From fc4526495a5288b86e4fd940dc6a68ca8e6cda2b Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 00:18:26 +0100 Subject: [PATCH 09/25] Bootstrap OpenSwarm before agent imports - run project bootstrap before third-party swarm imports - prove onboarding add-on env writes with a focused smoke - remove an npm script that is not shipped in the package --- onboard.py | 5 + package.json | 3 +- run_utils.py | 4 +- scripts/smoke-bootstrap-onboard.py | 151 +++++++++++++++++++++++++++++ swarm.py | 5 + 5 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 scripts/smoke-bootstrap-onboard.py diff --git a/onboard.py b/onboard.py index 3a838b8..f666886 100644 --- a/onboard.py +++ b/onboard.py @@ -26,6 +26,11 @@ _HAS_QUESTIONARY = True except ImportError: + class Choice: + def __init__(self, title: object, value: object | None = None) -> None: + self.title = title + self.value = title if value is None else value + _HAS_QUESTIONARY = False console = Console() diff --git a/package.json b/package.json index 1f07ed8..16bc651 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,7 @@ "package-lock.json" ], "scripts": { - "postinstall": "node -e \"const fs=require('fs');const path=require('path');const cp=require('child_process');const pkg=__dirname;const patchTarget=path.join(pkg,'node_modules','dom-to-pptx');const patchCli=path.join(pkg,'node_modules','patch-package','index.js');if(fs.existsSync(patchTarget)&&fs.existsSync(patchCli)){cp.execFileSync(process.execPath,[patchCli],{cwd:pkg,stdio:'inherit'});}try{fs.chmodSync(path.join(pkg,'bin','openswarm'),0o755)}catch(e){}\"", - "smoke:run-mode": "python3 scripts/smoke-run-mode.py" + "postinstall": "node -e \"const fs=require('fs');const path=require('path');const cp=require('child_process');const pkg=__dirname;const patchTarget=path.join(pkg,'node_modules','dom-to-pptx');const patchCli=path.join(pkg,'node_modules','patch-package','index.js');if(fs.existsSync(patchTarget)&&fs.existsSync(patchCli)){cp.execFileSync(process.execPath,[patchCli],{cwd:pkg,stdio:'inherit'});}try{fs.chmodSync(path.join(pkg,'bin','openswarm'),0o755)}catch(e){}\"" }, "dependencies": { "@vrsen/agentswarm": "1.4.34", diff --git a/run_utils.py b/run_utils.py index fb28e0e..e97814b 100644 --- a/run_utils.py +++ b/run_utils.py @@ -79,8 +79,8 @@ def _uv_env() -> dict[str, str]: # ── Bootstrap: create venv + install deps automatically on first run ───────── # Only stdlib imports above. _bootstrap() is called explicitly — either from -# swarm.py (via `from run import _bootstrap; _bootstrap()`) or from the -# __main__ guard below — never at module level, so `from run import _bootstrap` +# swarm.py (via `from run_utils import _bootstrap; _bootstrap()`) or from the +# __main__ guard below — never at module level, so `from run_utils import _bootstrap` # is safe to call from outside the venv. def _bootstrap() -> None: _repo = Path(__file__).resolve().parent diff --git a/scripts/smoke-bootstrap-onboard.py b/scripts/smoke-bootstrap-onboard.py new file mode 100644 index 0000000..be17822 --- /dev/null +++ b/scripts/smoke-bootstrap-onboard.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Focused smoke checks for OpenSwarm launch bootstrap and onboarding writes.""" + +from __future__ import annotations + +import importlib.util +import io +import os +import sys +import tempfile +import types +from collections.abc import Iterator +from contextlib import contextmanager +from pathlib import Path +from unittest.mock import patch + + +ROOT = Path(__file__).resolve().parents[1] + + +@contextmanager +def swapped_modules(replacements: dict[str, types.ModuleType]) -> Iterator[None]: + marker = object() + previous = {name: sys.modules.get(name, marker) for name in replacements} + sys.modules.update(replacements) + try: + yield + finally: + for name, module in previous.items(): + if module is marker: + sys.modules.pop(name, None) + else: + sys.modules[name] = module + + +def module(name: str, **attrs: object) -> types.ModuleType: + mod = types.ModuleType(name) + for key, value in attrs.items(): + setattr(mod, key, value) + return mod + + +def smoke_swarm_bootstrap_order() -> None: + order: list[str] = [] + + patches = module("patches", __path__=[]) + replacements = { + "run_utils": module("run_utils", _bootstrap=lambda: order.append("bootstrap")), + "dotenv": module("dotenv", load_dotenv=lambda: order.append("dotenv")), + "agents": module( + "agents", + set_tracing_disabled=lambda _value: order.append("agents"), + set_tracing_export_api_key=lambda _value: order.append("agents"), + ), + "patches": patches, + "patches.patch_agency_swarm_dual_comms": module( + "patches.patch_agency_swarm_dual_comms", + apply_dual_comms_patch=lambda: order.append("patch"), + ), + "patches.patch_file_attachment_refs": module( + "patches.patch_file_attachment_refs", + apply_file_attachment_reference_patch=lambda: order.append("patch"), + ), + "patches.patch_ipython_interpreter_composio": module( + "patches.patch_ipython_interpreter_composio", + apply_ipython_composio_context_patch=lambda: order.append("patch"), + ), + "patches.patch_utf8_file_reads": module( + "patches.patch_utf8_file_reads", + apply_utf8_file_read_patch=lambda: order.append("patch"), + ), + } + + spec = importlib.util.spec_from_file_location("swarm_bootstrap_smoke", ROOT / "swarm.py") + if not spec or not spec.loader: + raise RuntimeError("could not load swarm.py import spec") + + old_key = os.environ.pop("OPENAI_API_KEY", None) + try: + with swapped_modules(replacements): + swarm = importlib.util.module_from_spec(spec) + spec.loader.exec_module(swarm) + finally: + if old_key is not None: + os.environ["OPENAI_API_KEY"] = old_key + sys.modules.pop("swarm_bootstrap_smoke", None) + + if not order or order[0] != "bootstrap": + raise RuntimeError( + f"swarm.py did not run bootstrap before third-party imports: {order}" + ) + + +def smoke_onboard_env_writes() -> None: + sys.path.insert(0, str(ROOT)) + try: + import onboard + from rich.console import Console + finally: + sys.path.pop(0) + + provider = next(item for item in onboard.PROVIDERS if item["name"] == "OpenAI") + secrets = iter( + [ + "sk-test-openai", + "search-test-key", + "composio-test-key", + "composio-test-user", + ] + ) + + with tempfile.TemporaryDirectory(prefix="openswarm-onboard-smoke-") as tmp: + env = Path(tmp) / ".env" + sink = io.StringIO() + with ( + patch.object(onboard, "ENV_PATH", env), + patch.object(onboard, "console", Console(file=sink, force_terminal=False)), + patch.object(onboard, "_ask_select", lambda _message, _choices: provider), + patch.object( + onboard, + "_ask_checkbox", + lambda _message, _choices: ["search", "composio"], + ), + patch.object(onboard, "_ask_secret", lambda _label, _url: next(secrets)), + patch.object(onboard, "_ask_confirm", lambda _message, default=True: default), + ): + onboard.run_onboarding() + + values = onboard.dotenv_values(str(env)) + + expected = { + "OPENAI_API_KEY": "sk-test-openai", + "DEFAULT_MODEL": provider["default_model"], + "SEARCH_API_KEY": "search-test-key", + "COMPOSIO_API_KEY": "composio-test-key", + "COMPOSIO_USER_ID": "composio-test-user", + } + missing = {key: value for key, value in expected.items() if values.get(key) != value} + if missing: + raise RuntimeError(f"onboarding did not write expected .env values: {missing}") + + +def main() -> int: + smoke_swarm_bootstrap_order() + smoke_onboard_env_writes() + print("OpenSwarm bootstrap and onboarding smoke passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/swarm.py b/swarm.py index 7ddfde4..00835df 100644 --- a/swarm.py +++ b/swarm.py @@ -1,4 +1,9 @@ import os + +from run_utils import _bootstrap + +_bootstrap() + from dotenv import load_dotenv from agents import set_tracing_disabled, set_tracing_export_api_key from patches.patch_agency_swarm_dual_comms import apply_dual_comms_patch From 681bc2a558d4b7874f4fc166d031a190835146a4 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 00:34:19 +0100 Subject: [PATCH 10/25] Use latest AgentSwarm package for OpenSwarm - match the published OpenSwarm rc dependency shape - avoid freezing OpenSwarm on AgentSwarm 1.4.34 after the 1.4.35 npm publish - keep the lockfile consistent with current npm latest --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c449302..f6aa28c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@vrsen/agentswarm": "1.4.34", + "@vrsen/agentswarm": "latest", "dom-to-pptx": "1.1.5", "patch-package": "^8.0.1", "playwright": "^1.59.1", diff --git a/package.json b/package.json index 16bc651..146a46d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "postinstall": "node -e \"const fs=require('fs');const path=require('path');const cp=require('child_process');const pkg=__dirname;const patchTarget=path.join(pkg,'node_modules','dom-to-pptx');const patchCli=path.join(pkg,'node_modules','patch-package','index.js');if(fs.existsSync(patchTarget)&&fs.existsSync(patchCli)){cp.execFileSync(process.execPath,[patchCli],{cwd:pkg,stdio:'inherit'});}try{fs.chmodSync(path.join(pkg,'bin','openswarm'),0o755)}catch(e){}\"" }, "dependencies": { - "@vrsen/agentswarm": "1.4.34", + "@vrsen/agentswarm": "latest", "dom-to-pptx": "1.1.5", "patch-package": "^8.0.1", "playwright": "^1.59.1", From d6ff3f8c3f4d719ca4d80014e40dae71f5648d9c Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 21:34:15 +0100 Subject: [PATCH 11/25] Refresh OpenSwarm lockfile for AgentSwarm 1.4.35 - resolve @vrsen/agentswarm latest to 1.4.35 after npm publish - update transitive agentswarm-cli platform package locks --- package-lock.json | 110 +++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6aa28c..9ca2b16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -496,21 +496,21 @@ } }, "node_modules/@vrsen/agentswarm": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm/-/agentswarm-1.4.34.tgz", - "integrity": "sha512-6cLzI/4I//Q2LeU90VG54zD6mqdGJ5OcdpXB7xIyrNgfJx6LTHjZ4qmvwKvaS+ZdMs43BZkr+7v9FSeVtvMWbw==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm/-/agentswarm-1.4.35.tgz", + "integrity": "sha512-4pf5nUTlOSaUSt/zo08IU/Ygt5YVegMW/2Wnx54WIwNjoeytggr150me8B4WTmK0RliapV7bd3sQDYVbKkkmEQ==", "license": "MIT", "dependencies": { - "agentswarm-cli": "1.4.34" + "agentswarm-cli": "1.4.35" }, "bin": { "agentswarm": "bin/agentswarm" } }, "node_modules/@vrsen/agentswarm-cli-darwin-arm64": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-darwin-arm64/-/agentswarm-cli-darwin-arm64-1.4.34.tgz", - "integrity": "sha512-UHUB3yHS4RdFeywjtxfYbvpFhi0R9qqKsFEpIf4ADyVB4h+Ez2SGpRHVehR06ocuUOsUAbPOEvi9LHykvCok4Q==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-darwin-arm64/-/agentswarm-cli-darwin-arm64-1.4.35.tgz", + "integrity": "sha512-Zbu+2FUmWRr0qZ1lLArprGFeLB7cZjqkh4dFXlF9BRG3nX1DnOpTBZCBpjT7qE2Kz5ORbyIVUVhAp63w2t4Xdg==", "cpu": [ "arm64" ], @@ -520,9 +520,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-darwin-x64": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-darwin-x64/-/agentswarm-cli-darwin-x64-1.4.34.tgz", - "integrity": "sha512-cck3Cz4arffd0QoyrMvMbMArwNLAKc4NJBHnO2SHLEjQusUDTKRZt+XcHg/ygYx6RplHaIYHyFkl47cXjF+uAQ==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-darwin-x64/-/agentswarm-cli-darwin-x64-1.4.35.tgz", + "integrity": "sha512-WPNQ6luTDP6+qsEYT/qu77M5uxnQ37/+uMv/hOIZlLRJ0eccaoiJxNiSTX0s/fr3xR6ZikF/1j3XxOvjYle/7w==", "cpu": [ "x64" ], @@ -532,9 +532,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-darwin-x64-baseline": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-darwin-x64-baseline/-/agentswarm-cli-darwin-x64-baseline-1.4.34.tgz", - "integrity": "sha512-wO+9EKyPPY3PnVePzfq262j8xGoeXO0PoUs7py/brFV9vtacQ2mb/saYwJtADTKQWuudjdMT02m/c46mky6P1Q==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-darwin-x64-baseline/-/agentswarm-cli-darwin-x64-baseline-1.4.35.tgz", + "integrity": "sha512-F5wOFJ1pV4BGBJAvF2KvSWMekZsqXixWGCfGWdIwnbEBw9Aj+knmFxRKZQF3bnzuwMCPvEIp9L3Dz3TxgpUutg==", "cpu": [ "x64" ], @@ -544,9 +544,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-linux-arm64": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-arm64/-/agentswarm-cli-linux-arm64-1.4.34.tgz", - "integrity": "sha512-WNIiz6CQlo7nBxtTAr6+J18bT9mWn4wh64oMVO0GEdrdB7TR6HxUOnf91N3Y1JOoqEhTSYcke7VnDddt5Oa0rg==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-arm64/-/agentswarm-cli-linux-arm64-1.4.35.tgz", + "integrity": "sha512-o6acJoznCG5Xknwh9eK/LZlCttkJCKwAXSSe+cvXewo11thjOP+7ho9rmzclyeIJkUU5xGPq4fhgYH77TnB6Jg==", "cpu": [ "arm64" ], @@ -556,9 +556,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-linux-arm64-musl": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-arm64-musl/-/agentswarm-cli-linux-arm64-musl-1.4.34.tgz", - "integrity": "sha512-khcWx+nmFfxaKfLD5OuSUrEKe3HV7sFQURXFvcxTjxwFFMGoArRR/HZO+Oe2Y+Vip+cO6XAhTMQVXUHkhSOxQA==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-arm64-musl/-/agentswarm-cli-linux-arm64-musl-1.4.35.tgz", + "integrity": "sha512-WcvKaC1uniiOUWaiESgjuyf78Yj0Mw5SZwFYO6x0KnrSw8WNHsolouXv/VPyu4VrMKVFMAlksYn9RCPkpZz2fQ==", "cpu": [ "arm64" ], @@ -568,9 +568,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-linux-x64": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64/-/agentswarm-cli-linux-x64-1.4.34.tgz", - "integrity": "sha512-Tc3b/td5jDprlz7ByifPvea8cPKXW/4kXr0K8gSxbYor7qs1j31KEqguCSKUh5X4De0+cbfMJU5kf6rcbWRnSw==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64/-/agentswarm-cli-linux-x64-1.4.35.tgz", + "integrity": "sha512-reuVCj8wXHQSlA/Ve8sjLyftopgdpxGHf0ku6/SQ55uy+FFcTk8N+l+0+yrfgen7z4uV7m9gPU6KT9krAMY/RA==", "cpu": [ "x64" ], @@ -580,9 +580,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-linux-x64-baseline": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64-baseline/-/agentswarm-cli-linux-x64-baseline-1.4.34.tgz", - "integrity": "sha512-Vi7gyUPEvWfwzG6CUfWGPyeI7FEHZ6Xar6Tnax3fDu9XakeVjNLPJByfWAEepYONLlZ/2asSISgsp9PJpkwsYg==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64-baseline/-/agentswarm-cli-linux-x64-baseline-1.4.35.tgz", + "integrity": "sha512-37R9TxyXFABmccXXdq6bVv8qKzeh1JgHxyWMi+uDEu7rH4mwFT/K6ZmREDpXJfVlwfmzMAUPJ4d0twZkuDXIKg==", "cpu": [ "x64" ], @@ -592,9 +592,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-linux-x64-baseline-musl": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64-baseline-musl/-/agentswarm-cli-linux-x64-baseline-musl-1.4.34.tgz", - "integrity": "sha512-UPUcGhA2Z+VTXsqk6grweVEoffB0wN8j9bT4I5vhHP1jMFJJU34NSRZG07Tfc/l7AgRyEXquC52YUHn8KA5z8g==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64-baseline-musl/-/agentswarm-cli-linux-x64-baseline-musl-1.4.35.tgz", + "integrity": "sha512-bzROAlnVsF4Ww3N/C+Vt0jeUtN4oTcnahzRQyOMtpelspuGPhxW7uvfyHcmRC+hYZdFbP7628+2TMAiSp2ewUw==", "cpu": [ "x64" ], @@ -604,9 +604,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-linux-x64-musl": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64-musl/-/agentswarm-cli-linux-x64-musl-1.4.34.tgz", - "integrity": "sha512-986/egNVEIhb7C4WWDJQ20CeDarUw6J//kSVR7Rwrd1Pu2sOdi9rS8VnwlUU5QqPGxk0Zh1fnFCyFxngA2bmbA==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-linux-x64-musl/-/agentswarm-cli-linux-x64-musl-1.4.35.tgz", + "integrity": "sha512-nGrT7C+gtoBWLRhpD7etoeem8nz6XIC8wq7AzSrHk4Ii9nQkyqZ3fcScP3haputtB4CKyb4OYHmPQGseSzAsZw==", "cpu": [ "x64" ], @@ -616,9 +616,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-windows-arm64": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-windows-arm64/-/agentswarm-cli-windows-arm64-1.4.34.tgz", - "integrity": "sha512-xDAiwz38fYEwK8tzqGBSEhesefu1xVrER/U/5jmIx7K4ldd3gPShLo7PWnJq7UOabtSA2b78yE7rfm/GhoqijA==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-windows-arm64/-/agentswarm-cli-windows-arm64-1.4.35.tgz", + "integrity": "sha512-cUDPQ8uN7fxowsJwE5Q+bN0g+vNDR9+Oe8NR7atsII40D4ERDZ4IRJCrJO37P1yaIz041dprNsYZ9aM5BjT78g==", "cpu": [ "arm64" ], @@ -628,9 +628,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-windows-x64": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-windows-x64/-/agentswarm-cli-windows-x64-1.4.34.tgz", - "integrity": "sha512-/Yuhtv8VetHfhPySPXwBTzyCxZeXdJ+W+t4klkmIJGDxPiZqkXc00vBzM4f+mgvwDNmh3F8y4sOUIQOcKXkz3Q==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-windows-x64/-/agentswarm-cli-windows-x64-1.4.35.tgz", + "integrity": "sha512-+Ddu9W44GDTD4A/KHoSKtl7CYS8NfPdzCx13VNhlOZhBCaGzMDOAherHwj4KaXYV8thaENjQX5C8ckuxI1v0bA==", "cpu": [ "x64" ], @@ -640,9 +640,9 @@ ] }, "node_modules/@vrsen/agentswarm-cli-windows-x64-baseline": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-windows-x64-baseline/-/agentswarm-cli-windows-x64-baseline-1.4.34.tgz", - "integrity": "sha512-x3CguvjJW8ascO13MnzDpZeT+qqs3NiUWnt+Z5mDOdITcYaCTc5CkncNCBYHRLgtLzAeMt/qcADwZRb1I5G4PQ==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/@vrsen/agentswarm-cli-windows-x64-baseline/-/agentswarm-cli-windows-x64-baseline-1.4.35.tgz", + "integrity": "sha512-Pi/UvIMAHf58fZ0Pm/ZfsUSrIsxCTm++v8nPDsGDmK1Q9JLSt2+ZYZVQiBE/S+AHFGDDJvhEQtMtKqj6qRwKEA==", "cpu": [ "x64" ], @@ -667,27 +667,27 @@ "license": "BSD-2-Clause" }, "node_modules/agentswarm-cli": { - "version": "1.4.34", - "resolved": "https://registry.npmjs.org/agentswarm-cli/-/agentswarm-cli-1.4.34.tgz", - "integrity": "sha512-wYLUFpsElbzz10gLB6KB6GNgd0j4SIkckiXnmy0onCWMoHoCZv8LA4VjX5iiOtT4HH1V7xP2DHJwMqAgSKKAHQ==", + "version": "1.4.35", + "resolved": "https://registry.npmjs.org/agentswarm-cli/-/agentswarm-cli-1.4.35.tgz", + "integrity": "sha512-QplH7PG/XgXBpOAykNiSMDNt7sH099anaVs9aST7lhr6Y9E958zClIDcdkJvGGCy8Nj/WUEmO/CsyMdnaes3ug==", "hasInstallScript": true, "license": "MIT", "bin": { "agentswarm": "bin/agentswarm" }, "optionalDependencies": { - "@vrsen/agentswarm-cli-darwin-arm64": "1.4.34", - "@vrsen/agentswarm-cli-darwin-x64": "1.4.34", - "@vrsen/agentswarm-cli-darwin-x64-baseline": "1.4.34", - "@vrsen/agentswarm-cli-linux-arm64": "1.4.34", - "@vrsen/agentswarm-cli-linux-arm64-musl": "1.4.34", - "@vrsen/agentswarm-cli-linux-x64": "1.4.34", - "@vrsen/agentswarm-cli-linux-x64-baseline": "1.4.34", - "@vrsen/agentswarm-cli-linux-x64-baseline-musl": "1.4.34", - "@vrsen/agentswarm-cli-linux-x64-musl": "1.4.34", - "@vrsen/agentswarm-cli-windows-arm64": "1.4.34", - "@vrsen/agentswarm-cli-windows-x64": "1.4.34", - "@vrsen/agentswarm-cli-windows-x64-baseline": "1.4.34" + "@vrsen/agentswarm-cli-darwin-arm64": "1.4.35", + "@vrsen/agentswarm-cli-darwin-x64": "1.4.35", + "@vrsen/agentswarm-cli-darwin-x64-baseline": "1.4.35", + "@vrsen/agentswarm-cli-linux-arm64": "1.4.35", + "@vrsen/agentswarm-cli-linux-arm64-musl": "1.4.35", + "@vrsen/agentswarm-cli-linux-x64": "1.4.35", + "@vrsen/agentswarm-cli-linux-x64-baseline": "1.4.35", + "@vrsen/agentswarm-cli-linux-x64-baseline-musl": "1.4.35", + "@vrsen/agentswarm-cli-linux-x64-musl": "1.4.35", + "@vrsen/agentswarm-cli-windows-arm64": "1.4.35", + "@vrsen/agentswarm-cli-windows-x64": "1.4.35", + "@vrsen/agentswarm-cli-windows-x64-baseline": "1.4.35" } }, "node_modules/ansi-styles": { From f95b6a634bf0502adfb537dffa512359de5c524a Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 22:46:42 +0100 Subject: [PATCH 12/25] Pin AgentSwarm release dependency - pin OpenSwarm runtime dependency to AgentSwarm 1.4.35 - refresh package-lock root dependency spec after npm publish --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ca2b16..83c08da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@vrsen/agentswarm": "latest", + "@vrsen/agentswarm": "1.4.35", "dom-to-pptx": "1.1.5", "patch-package": "^8.0.1", "playwright": "^1.59.1", diff --git a/package.json b/package.json index 146a46d..4bac16e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "postinstall": "node -e \"const fs=require('fs');const path=require('path');const cp=require('child_process');const pkg=__dirname;const patchTarget=path.join(pkg,'node_modules','dom-to-pptx');const patchCli=path.join(pkg,'node_modules','patch-package','index.js');if(fs.existsSync(patchTarget)&&fs.existsSync(patchCli)){cp.execFileSync(process.execPath,[patchCli],{cwd:pkg,stdio:'inherit'});}try{fs.chmodSync(path.join(pkg,'bin','openswarm'),0o755)}catch(e){}\"" }, "dependencies": { - "@vrsen/agentswarm": "latest", + "@vrsen/agentswarm": "1.4.35", "dom-to-pptx": "1.1.5", "patch-package": "^8.0.1", "playwright": "^1.59.1", From 4bee00937933c60187e06d41f55046fcc1dc2123 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 22:50:25 +0100 Subject: [PATCH 13/25] Build OpenSwarm arm64 release assets - add Linux arm64 and Windows arm64 TUI build matrix entries - match launcher asset names for supported AgentSwarm platforms --- .github/workflows/build-tui.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index 714fed0..ef7bcc8 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -117,9 +117,15 @@ jobs: - os: ubuntu-latest platform: linux arch: x64 + - os: ubuntu-24.04-arm + platform: linux + arch: arm64 - os: windows-latest platform: windows arch: x64 + - os: windows-11-arm + platform: windows + arch: arm64 runs-on: ${{ matrix.os }} steps: From 95aefdc87d8f830dc4f6ffe0b7fb6196a0bc088d Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 22:53:37 +0100 Subject: [PATCH 14/25] Package OpenSwarm run utilities - include run_utils in Python module metadata - keep Python package imports aligned with swarm bootstrap --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index efba6cb..e0f6e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ dependencies = [ openswarm = "run_utils:main" [tool.setuptools] -py-modules = ["agency", "swarm", "helpers", "config", "onboard", "server"] +py-modules = ["agency", "swarm", "helpers", "config", "onboard", "server", "run_utils"] [tool.setuptools.packages.find] where = ["."] From 4614c9e93809955df604faceca7f543b1898c586 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 22:56:48 +0100 Subject: [PATCH 15/25] Align Python OpenSwarm product env - set skip post-auth model selection for Python launches - keep npm and Python OpenSwarm TUI behavior consistent --- run_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/run_utils.py b/run_utils.py index e97814b..1e01530 100644 --- a/run_utils.py +++ b/run_utils.py @@ -35,6 +35,7 @@ def _configure_product_env() -> None: + os.environ.setdefault("AGENTSWARM_PRODUCT_SKIP_POST_AUTH_MODEL_SELECTION", "true") os.environ.setdefault("AGENTSWARM_PRODUCT_TUI_LOGO_LEFT", _PRODUCT_TUI_LOGO_LEFT) os.environ.setdefault("AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT", _PRODUCT_TUI_LOGO_RIGHT) os.environ.setdefault("AGENTSWARM_PRODUCT_WORDMARK_LINES", _PRODUCT_WORDMARK_LINES) From 81dae452a80e91160124fe85c6171a5cdde3b5e7 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 23:02:08 +0100 Subject: [PATCH 16/25] Use downloaded OpenSwarm TUI in Python paths - export AGENTSWARM_BIN after bootstrap downloads or finds the TUI binary - allow smoke helper to install Linux and Windows arm64 OpenSwarm TUI assets --- run_utils.py | 2 ++ scripts/smoke-run-mode.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/run_utils.py b/run_utils.py index 1e01530..1275a45 100644 --- a/run_utils.py +++ b/run_utils.py @@ -190,6 +190,8 @@ def _bootstrap() -> None: print("\nDone.\n") except Exception: print("Warning: Could not download OpenSwarm TUI. The terminal UI will use the default.\n") + if _bin_path.exists(): + os.environ.setdefault("AGENTSWARM_BIN", str(_bin_path)) # ───────────────────────────────────────────────────────────────────────────── diff --git a/scripts/smoke-run-mode.py b/scripts/smoke-run-mode.py index 661609b..ee29f5c 100644 --- a/scripts/smoke-run-mode.py +++ b/scripts/smoke-run-mode.py @@ -90,10 +90,10 @@ def platform_asset_name() -> str: if sys.platform == "darwin" and arch: return f"agentswarm-darwin-{arch}" - if sys.platform.startswith("linux") and arch == "x64": - return "agentswarm-linux-x64" - if sys.platform == "win32" and arch == "x64": - return "agentswarm-windows-x64.exe" + if sys.platform.startswith("linux") and arch: + return f"agentswarm-linux-{arch}" + if sys.platform == "win32" and arch: + return f"agentswarm-windows-{arch}.exe" raise RuntimeError( f"unsupported OpenSwarm TUI asset platform: {sys.platform} {platform.machine()}" ) From d732409d8e93a78d595cefda844a678d15ed8522 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 23:05:55 +0100 Subject: [PATCH 17/25] Require all OpenSwarm release assets - include Linux arm64 and Windows arm64 binaries in the publish gate - prevent npm publish when a supported OpenSwarm TUI asset is missing --- .github/workflows/build-tui.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index ef7bcc8..5ddc1ad 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -295,7 +295,9 @@ jobs: required_assets=( agentswarm-darwin-arm64 agentswarm-darwin-x64 + agentswarm-linux-arm64 agentswarm-linux-x64 + agentswarm-windows-arm64.exe agentswarm-windows-x64.exe ) From 420596bf6a29e4183d4258bf1a8ccc2e4389afba Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sat, 23 May 2026 23:10:54 +0100 Subject: [PATCH 18/25] Publish baseline OpenSwarm TUI assets - build and require baseline x64 release assets - try baseline OpenSwarm TUI binary when the regular x64 binary is unavailable or invalid --- .github/workflows/build-tui.yml | 44 +++++++++++++++++++++++++----- bin/openswarm | 47 ++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index 5ddc1ad..b0297da 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -111,21 +111,48 @@ jobs: - os: macos-latest platform: darwin arch: arm64 + baseline: false + asset: agentswarm-darwin-arm64 - os: macos-15-intel platform: darwin arch: x64 + baseline: false + asset: agentswarm-darwin-x64 + - os: macos-15-intel + platform: darwin + arch: x64 + baseline: true + asset: agentswarm-darwin-x64-baseline - os: ubuntu-latest platform: linux arch: x64 + baseline: false + asset: agentswarm-linux-x64 + - os: ubuntu-latest + platform: linux + arch: x64 + baseline: true + asset: agentswarm-linux-x64-baseline - os: ubuntu-24.04-arm platform: linux arch: arm64 + baseline: false + asset: agentswarm-linux-arm64 + - os: windows-latest + platform: windows + arch: x64 + baseline: false + asset: agentswarm-windows-x64.exe - os: windows-latest platform: windows arch: x64 + baseline: true + asset: agentswarm-windows-x64-baseline.exe - os: windows-11-arm platform: windows arch: arm64 + baseline: false + asset: agentswarm-windows-arm64.exe runs-on: ${{ matrix.os }} steps: @@ -163,26 +190,26 @@ jobs: AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_VERSION: ${{ needs.prepare.outputs.release_version }} - run: bun run script/build.ts --single --skip-install + run: bun run script/build.ts --single --skip-install ${{ matrix.baseline && '--baseline' || '' }} - name: Rename binary (Unix) if: matrix.platform != 'windows' run: | - mv agentswarm-cli/packages/opencode/dist/agentswarm-cli-${{ matrix.platform }}-${{ matrix.arch }}/bin/agentswarm \ - agentswarm-${{ matrix.platform }}-${{ matrix.arch }} + mv agentswarm-cli/packages/opencode/dist/agentswarm-cli-${{ matrix.platform }}-${{ matrix.arch }}${{ matrix.baseline && '-baseline' || '' }}/bin/agentswarm \ + ${{ matrix.asset }} - name: Rename binary (Windows) if: matrix.platform == 'windows' shell: pwsh run: | - Move-Item agentswarm-cli/packages/opencode/dist/agentswarm-cli-${{ matrix.platform }}-${{ matrix.arch }}/bin/agentswarm.exe ` - agentswarm-${{ matrix.platform }}-${{ matrix.arch }}.exe + Move-Item agentswarm-cli/packages/opencode/dist/agentswarm-cli-${{ matrix.platform }}-${{ matrix.arch }}${{ matrix.baseline && '-baseline' || '' }}/bin/agentswarm.exe ` + ${{ matrix.asset }} - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: agentswarm-${{ matrix.platform }}-${{ matrix.arch }} - path: agentswarm-${{ matrix.platform }}-${{ matrix.arch }}${{ matrix.platform == 'windows' && '.exe' || '' }} + name: ${{ matrix.asset }} + path: ${{ matrix.asset }} if-no-files-found: error release: @@ -295,10 +322,13 @@ jobs: required_assets=( agentswarm-darwin-arm64 agentswarm-darwin-x64 + agentswarm-darwin-x64-baseline agentswarm-linux-arm64 agentswarm-linux-x64 + agentswarm-linux-x64-baseline agentswarm-windows-arm64.exe agentswarm-windows-x64.exe + agentswarm-windows-x64-baseline.exe ) for required in "${required_assets[@]}"; do diff --git a/bin/openswarm b/bin/openswarm index 1a4e474..6d66cbb 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -66,8 +66,15 @@ function resolveAgentswarm(startDir) { } } -function getBinaryName() { - return process.platform === 'win32' ? `agentswarm-windows-${os.arch() === 'arm64' ? 'arm64' : 'x64'}.exe` : `agentswarm-${process.platform === 'darwin' ? 'darwin' : 'linux'}-${os.arch() === 'arm64' ? 'arm64' : 'x64'}` +function getBinaryNames() { + const arch = os.arch() === 'arm64' ? 'arm64' : 'x64' + const platform = process.platform === 'win32' ? 'windows' : process.platform === 'darwin' ? 'darwin' : 'linux' + const extension = process.platform === 'win32' ? '.exe' : '' + const names = [`agentswarm-${platform}-${arch}${extension}`] + if (arch === 'x64') { + names.push(`agentswarm-${platform}-${arch}-baseline${extension}`) + } + return names } function getDownloadUrl(binaryName) { @@ -175,29 +182,31 @@ async function download(url, destination) { } async function ensureCustomBinary() { - const binaryName = getBinaryName() - const binaryPath = path.join(packageDir, binaryName) - const existing = resolveCustomBinary(binaryPath) - if (existing) return existing - - fs.rmSync(binaryPath, { force: true }) + const binaryNames = getBinaryNames() + for (const binaryName of binaryNames) { + const existing = resolveCustomBinary(path.join(packageDir, binaryName)) + if (existing) return existing + } - const url = getDownloadUrl(binaryName) process.stdout.write('Downloading OpenSwarm TUI...\n') - try { - await download(url, binaryPath) - const resolved = resolveCustomBinary(binaryPath) - if (!resolved) { + const errors = [] + for (const binaryName of binaryNames) { + const binaryPath = path.join(packageDir, binaryName) + fs.rmSync(binaryPath, { force: true }) + try { + await download(getDownloadUrl(binaryName), binaryPath) + const resolved = resolveCustomBinary(binaryPath) + if (resolved) return resolved fs.rmSync(binaryPath, { force: true }) throw new Error('Downloaded custom TUI binary failed validation') + } catch (error) { + fs.rmSync(binaryPath, { force: true }) + errors.push(`${binaryName}: ${error instanceof Error ? error.message : String(error)}`) } - return resolved - } catch (error) { - fs.rmSync(binaryPath, { force: true }) - throw new Error( - `custom OpenSwarm TUI unavailable (${error instanceof Error ? error.message : String(error)}). Reinstall @vrsen/openswarm or set OPENSWARM_TUI_URL to a valid binary.`, - ) } + throw new Error( + `custom OpenSwarm TUI unavailable (${errors.join('; ')}). Reinstall @vrsen/openswarm or set OPENSWARM_TUI_URL to a valid binary.`, + ) } const agentswarmBin = resolveAgentswarm(packageDir) From f0437dd9ab3d5f04f4b5911277064c0a8a0b143e Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 02:43:52 +0100 Subject: [PATCH 19/25] fix: use standalone Python for OpenSwarm launch - opt OpenSwarm into the standalone product Python policy - add a handoff follow-up smoke check for backend disconnects --- bin/openswarm | 1 + scripts/smoke-run-mode.py | 45 +++++++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/bin/openswarm b/bin/openswarm index 6d66cbb..8d544df 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -46,6 +46,7 @@ const downstreamEnv = { AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: JSON.stringify(productTuiLogoLeft), AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: JSON.stringify(productTuiLogoRight), AGENTSWARM_PRODUCT_WORDMARK_LINES: JSON.stringify(productWordmarkLines), + AGENTSWARM_PRODUCT_PYTHON_ENVIRONMENT: 'standalone', AGENTSWARM_PRODUCT_VERSION: packageJson.version, } diff --git a/scripts/smoke-run-mode.py b/scripts/smoke-run-mode.py index ee29f5c..ecbb28f 100644 --- a/scripts/smoke-run-mode.py +++ b/scripts/smoke-run-mode.py @@ -24,6 +24,9 @@ DEFAULT_PROMPT = "Reply exactly OPEN_SWARM_RUN_SMOKE_OK." DEFAULT_EXPECT = "OPEN_SWARM_RUN_SMOKE_OK" +HANDOFF_PROMPT = "handoff to the data analyst" +HANDOFF_FOLLOWUP_PROMPT = "Reply exactly OPEN_SWARM_HANDOFF_OK." +HANDOFF_EXPECT = "OPEN_SWARM_HANDOFF_OK" EXPECTED_AGENCY_NAME = "OpenSwarm" EXPECTED_ENTRY_AGENT = "Orchestrator" EXPECTED_SPECIALIST_AGENTS = [ @@ -262,8 +265,13 @@ def run_tui_smoke( closed_models_at: float | None = None sent_prompt = False saw_expected = False + sent_handoff_prompt = False + saw_handoff = False + handoff_seen_at: float | None = None + sent_handoff_followup = False deadline = time.monotonic() + timeout expected_compact = compact(expected) + handoff_expected_compact = compact(HANDOFF_EXPECT) expected_agent_terms = [EXPECTED_AGENCY_NAME, EXPECTED_ENTRY_AGENT, *EXPECTED_SPECIALIST_AGENTS] expected_agent_compact = [compact(term) for term in expected_agent_terms] @@ -280,6 +288,10 @@ def run_tui_smoke( plain = strip_ansi(raw) compact_plain = compact(plain) + lower_compact = compact_plain.lower() + + if "failedtostartresponsestream" in lower_compact or "cannotreachagency-swarmbackend" in lower_compact: + raise RuntimeError("OpenSwarm backend became unreachable during smoke test") if not sent_confirm and "Createalocal`.venv`inthisproject?" in compact_plain: write(master_fd, "\r") @@ -304,6 +316,27 @@ def run_tui_smoke( verified_agents = True closed_agents_at = closed_agents_at or time.monotonic() + if check == "handoff" and run_mode_ready and not sent_handoff_prompt: + write(master_fd, HANDOFF_PROMPT + "\r") + sent_handoff_prompt = True + + if sent_handoff_prompt and not saw_handoff: + if "transfer_to_Data_Analyst" in plain or "DataAnalyst" in compact_plain: + saw_handoff = True + handoff_seen_at = time.monotonic() + + if ( + saw_handoff + and not sent_handoff_followup + and handoff_seen_at is not None + and time.monotonic() - handoff_seen_at > 0.5 + ): + write(master_fd, HANDOFF_FOLLOWUP_PROMPT + "\r") + sent_handoff_followup = True + + if check == "handoff" and (HANDOFF_EXPECT in plain or handoff_expected_compact in compact_plain): + return plain + agents_ready = check not in {"agents", "all"} or verified_agents if ( check in {"models", "all"} @@ -398,7 +431,9 @@ def run_tui_smoke( f"saw_models_slash={saw_models_slash} cleared_models_probe={cleared_models_probe} " f"sent_models_direct={sent_models_direct} saw_models_direct={saw_models_direct} " f"selected_models_command={selected_models_command} " - f"verified_models={verified_models} sent_prompt={sent_prompt} saw_expected={saw_expected} log={log_path}" + f"verified_models={verified_models} sent_prompt={sent_prompt} saw_expected={saw_expected} " + f"sent_handoff_prompt={sent_handoff_prompt} saw_handoff={saw_handoff} " + f"sent_handoff_followup={sent_handoff_followup} log={log_path}" ) @@ -414,7 +449,7 @@ def main() -> int: "--openswarm-tui-binary", help="Local OpenSwarm-branded AgentSwarm TUI binary to copy into the installed @vrsen/openswarm package.", ) - parser.add_argument("--check", choices=["all", "agents", "models", "prompt"], default="all") + parser.add_argument("--check", choices=["all", "agents", "models", "prompt", "handoff"], default="all") parser.add_argument("--prompt", default=DEFAULT_PROMPT) parser.add_argument("--expect", default=DEFAULT_EXPECT) parser.add_argument("--timeout", type=int, default=1200) @@ -428,10 +463,10 @@ def main() -> int: openswarm_tui_binary = resolve_local_binary(args.openswarm_tui_binary) if args.openswarm_tui_binary else None models_fixture = resolve_models_fixture(agentswarm_path) api_key = os.environ.get("OPENAI_API_KEY") - if not api_key and args.check == "prompt" and os.environ.get("GITHUB_ACTIONS") == "true": + if not api_key and args.check in {"prompt", "handoff"} and os.environ.get("GITHUB_ACTIONS") == "true": print("Skipped OpenSwarm live prompt smoke because OPENAI_API_KEY is not configured") return 0 - if not api_key and args.check in {"all", "prompt"}: + if not api_key and args.check in {"all", "prompt", "handoff"}: raise RuntimeError("OPENAI_API_KEY is required for the live prompt smoke test") auth_key = api_key or "dummy-openai-key-for-agent-roster-smoke" root = pathlib.Path(tempfile.mkdtemp(prefix="openswarm-run-mode-smoke-")) @@ -483,6 +518,8 @@ def main() -> int: print("OpenSwarm /models smoke passed") if args.check in {"prompt", "all"}: print("OpenSwarm live prompt smoke passed") + if args.check == "handoff": + print("OpenSwarm handoff smoke passed") print(f"OpenSwarm smoke root package: {package_dir}") return 0 finally: From a2f150284c59ed174ff2d414dd99a3a719b92876 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 02:46:15 +0100 Subject: [PATCH 20/25] ci: build OpenSwarm with standalone Python policy - pass the product Python environment into OpenSwarm TUI builds --- .github/workflows/build-tui.yml | 1 + .github/workflows/live-run-mode-smoke.yml | 1 + .github/workflows/test-mac.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/build-tui.yml b/.github/workflows/build-tui.yml index b0297da..0314dcc 100644 --- a/.github/workflows/build-tui.yml +++ b/.github/workflows/build-tui.yml @@ -189,6 +189,7 @@ jobs: AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" "," ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' + AGENTSWARM_PRODUCT_PYTHON_ENVIRONMENT: standalone AGENTSWARM_PRODUCT_VERSION: ${{ needs.prepare.outputs.release_version }} run: bun run script/build.ts --single --skip-install ${{ matrix.baseline && '--baseline' || '' }} diff --git a/.github/workflows/live-run-mode-smoke.yml b/.github/workflows/live-run-mode-smoke.yml index e159100..848a6dc 100644 --- a/.github/workflows/live-run-mode-smoke.yml +++ b/.github/workflows/live-run-mode-smoke.yml @@ -60,6 +60,7 @@ jobs: AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" "," ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' + AGENTSWARM_PRODUCT_PYTHON_ENVIRONMENT: standalone AGENTSWARM_PRODUCT_VERSION: ${{ steps.openswarm.outputs.version }} run: | bun install diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 03cd127..9fd44e7 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -46,6 +46,7 @@ jobs: AGENTSWARM_PRODUCT_TUI_LOGO_LEFT: '[" "," ██████╗ ██████╗ ███████╗███╗ ██╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║","╚██████╔╝██║ ███████╗██║ ╚████║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝"]' AGENTSWARM_PRODUCT_TUI_LOGO_RIGHT: '["","███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║","███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║","╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' AGENTSWARM_PRODUCT_WORDMARK_LINES: '[""," ██████╗ ██████╗ ███████╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗","██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║","██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║","██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ╚════██║██║███╗██║██╔══██╗██╔══██╗██║╚██╔╝██║","╚██████╔╝██║ ███████╗██║ ╚████║ ███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║"," ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"]' + AGENTSWARM_PRODUCT_PYTHON_ENVIRONMENT: standalone AGENTSWARM_PRODUCT_VERSION: 1.0.1-rc.4 run: | bun install From e3a7f2b048ea928672f4ab0724e352dbc371228a Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 03:26:56 +0100 Subject: [PATCH 21/25] fix: honor OpenSwarm TUI env override before bootstrap - preload AGENTSWARM_BIN from .env before bootstrap chooses a default binary - keep package-script dotenv behavior unchanged --- swarm.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/swarm.py b/swarm.py index 00835df..9ce5e2b 100644 --- a/swarm.py +++ b/swarm.py @@ -1,7 +1,46 @@ import os +from pathlib import Path + + +def _preload_agentswarm_bin() -> None: + # Bootstrap may install python-dotenv, so preserve this one override with stdlib. + if "AGENTSWARM_BIN" in os.environ: + return + + try: + lines = ( + (Path(__file__).resolve().parent / ".env") + .read_text(encoding="utf-8") + .splitlines() + ) + except OSError: + return + + for line in lines: + value = line.strip() + if not value or value.startswith("#"): + continue + if value.startswith("export "): + value = value.removeprefix("export ").lstrip() + + key, sep, raw = value.partition("=") + if sep != "=" or key.strip() != "AGENTSWARM_BIN": + continue + + raw = raw.strip() + if raw[:1] in {"'", '"'}: + quote = raw[0] + end = raw.find(quote, 1) + raw = raw[1:end] if end != -1 else raw[1:] + else: + raw = raw.split(" #", 1)[0].strip() + os.environ["AGENTSWARM_BIN"] = raw + return + from run_utils import _bootstrap +_preload_agentswarm_bin() _bootstrap() from dotenv import load_dotenv From 554770f32dcb7b2618485bc1b07df8ba31c5add7 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 03:40:22 +0100 Subject: [PATCH 22/25] fix: use baseline OpenSwarm TUI from Python bootstrap - validate downloaded TUI binaries before exporting AGENTSWARM_BIN - try x64 baseline assets after normal x64 binaries - preserve explicit AGENTSWARM_BIN overrides --- run_utils.py | 81 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/run_utils.py b/run_utils.py index 1275a45..8854afb 100644 --- a/run_utils.py +++ b/run_utils.py @@ -53,6 +53,62 @@ def _resolve_bin_name() -> str: return f"agentswarm-linux-{arch}" +def _resolve_bin_names() -> list[str]: + name = _resolve_bin_name() + names = [name] + stem, suffix = (name[:-4], ".exe") if name.endswith(".exe") else (name, "") + if stem.endswith("-x64"): + names.append(f"{stem}-baseline{suffix}") + return names + + +def _is_tui_binary_runnable(path: Path) -> bool: + try: + stat = path.stat() + if not stat.st_size: + return False + if sys.platform != "win32" and not os.access(path, os.X_OK): + path.chmod(0o755) + result = subprocess.run( + [str(path), "--version"], + env={**os.environ, "AGENTSWARM_LAUNCHER": "0"}, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=15, + ) + except Exception: + return False + return result.returncode == 0 + + +def _download_tui_binary(repo: Path, name: str) -> Path | None: + import urllib.request + + path = repo / name + url = f"https://github.com/VRSEN/OpenSwarm/releases/latest/download/{name}" + print("Downloading OpenSwarm TUI, please wait…\n") + try: + urllib.request.urlretrieve(url, str(path)) + if sys.platform != "win32": + path.chmod(0o755) + print("\nDone.\n") + except Exception: + path.unlink(missing_ok=True) + return None + return path + + +def _resolve_tui_binary(repo: Path, download: bool) -> Path | None: + for name in _resolve_bin_names(): + path = repo / name + if not path.exists() and download: + path = _download_tui_binary(repo, name) or path + if path.exists() and _is_tui_binary_runnable(path): + return path + return None + + def _ensure_node_playwright_browsers(repo: Path) -> None: """Install Node Playwright browsers where the HTML-to-PPTX runner looks for them.""" cli = repo / "node_modules" / "playwright" / "cli.js" @@ -177,21 +233,12 @@ def _bootstrap() -> None: pass # Download the OpenSwarm TUI binary from GitHub Releases if missing. - _bin_name = _resolve_bin_name() - _bin_path = _repo / _bin_name - if not _bin_path.exists(): - import urllib.request - _bin_url = f"https://github.com/VRSEN/OpenSwarm/releases/latest/download/{_bin_name}" - print("Downloading OpenSwarm TUI, please wait…\n") - try: - urllib.request.urlretrieve(_bin_url, str(_bin_path)) - if sys.platform != "win32": - _bin_path.chmod(0o755) - print("\nDone.\n") - except Exception: - print("Warning: Could not download OpenSwarm TUI. The terminal UI will use the default.\n") - if _bin_path.exists(): - os.environ.setdefault("AGENTSWARM_BIN", str(_bin_path)) + if not os.getenv("AGENTSWARM_BIN"): + _bin_path = _resolve_tui_binary(_repo, download=True) + if _bin_path: + os.environ["AGENTSWARM_BIN"] = str(_bin_path) + else: + print("Warning: Could not download a runnable OpenSwarm TUI. The terminal UI will use the default.\n") # ───────────────────────────────────────────────────────────────────────────── @@ -279,8 +326,8 @@ def main() -> None: if not os.getenv("AGENTSWARM_BIN"): _repo = Path(__file__).resolve().parent - local_exe = _repo / _resolve_bin_name() - if local_exe.exists(): + local_exe = _resolve_tui_binary(_repo, download=True) + if local_exe: os.environ["AGENTSWARM_BIN"] = str(local_exe) # Disable OpenAI Agents SDK tracing for terminal demo runs. From c122b3e5cf974ec5497806c627a2a4ada2522b0e Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 03:55:01 +0100 Subject: [PATCH 23/25] fix: avoid bootstrap side effects on swarm imports - keep full bootstrap on CLI/TUI launch paths - preload AGENTSWARM_BIN before Python bootstrap when needed - update smoke proof that import does not run bootstrap --- run_utils.py | 46 ++++++++++++++- scripts/smoke-bootstrap-onboard.py | 22 +++++--- swarm.py | 89 +++++++++++++----------------- 3 files changed, 95 insertions(+), 62 deletions(-) diff --git a/run_utils.py b/run_utils.py index 8854afb..683fded 100644 --- a/run_utils.py +++ b/run_utils.py @@ -41,6 +41,48 @@ def _configure_product_env() -> None: os.environ.setdefault("AGENTSWARM_PRODUCT_WORDMARK_LINES", _PRODUCT_WORDMARK_LINES) +def _preload_agentswarm_bin(repo: Path | None = None) -> None: + # Bootstrap may install python-dotenv, so preserve this one override with stdlib. + if "AGENTSWARM_BIN" in os.environ: + return + + roots = [repo] if repo else [Path.cwd(), Path(__file__).resolve().parent] + seen: set[Path] = set() + + for root in roots: + if root is None: + continue + path = root.resolve() / ".env" + if path in seen: + continue + seen.add(path) + try: + lines = path.read_text(encoding="utf-8").splitlines() + except OSError: + continue + + for line in lines: + value = line.strip() + if not value or value.startswith("#"): + continue + if value.startswith("export "): + value = value.removeprefix("export ").lstrip() + + key, sep, raw = value.partition("=") + if sep != "=" or key.strip() != "AGENTSWARM_BIN": + continue + + raw = raw.strip() + if raw[:1] in {"'", '"'}: + quote = raw[0] + end = raw.find(quote, 1) + raw = raw[1:end] if end != -1 else raw[1:] + else: + raw = raw.split(" #", 1)[0].strip() + os.environ["AGENTSWARM_BIN"] = raw + return + + def _resolve_bin_name() -> str: """Return the platform+arch-specific TUI binary filename.""" import platform @@ -317,6 +359,9 @@ def _configure_demo_console() -> None: def main() -> None: + _preload_agentswarm_bin() + _bootstrap() + from dotenv import load_dotenv load_dotenv() @@ -389,5 +434,4 @@ def main() -> None: if __name__ == "__main__": - _bootstrap() main() diff --git a/scripts/smoke-bootstrap-onboard.py b/scripts/smoke-bootstrap-onboard.py index be17822..6480b18 100644 --- a/scripts/smoke-bootstrap-onboard.py +++ b/scripts/smoke-bootstrap-onboard.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Focused smoke checks for OpenSwarm launch bootstrap and onboarding writes.""" +"""Focused smoke checks for OpenSwarm import bootstrap and onboarding writes.""" from __future__ import annotations @@ -40,12 +40,16 @@ def module(name: str, **attrs: object) -> types.ModuleType: return mod -def smoke_swarm_bootstrap_order() -> None: +def smoke_swarm_import_skips_bootstrap() -> None: order: list[str] = [] patches = module("patches", __path__=[]) replacements = { - "run_utils": module("run_utils", _bootstrap=lambda: order.append("bootstrap")), + "run_utils": module( + "run_utils", + _bootstrap=lambda: order.append("bootstrap"), + _preload_agentswarm_bin=lambda: order.append("preload"), + ), "dotenv": module("dotenv", load_dotenv=lambda: order.append("dotenv")), "agents": module( "agents", @@ -85,10 +89,10 @@ def smoke_swarm_bootstrap_order() -> None: os.environ["OPENAI_API_KEY"] = old_key sys.modules.pop("swarm_bootstrap_smoke", None) - if not order or order[0] != "bootstrap": - raise RuntimeError( - f"swarm.py did not run bootstrap before third-party imports: {order}" - ) + if "bootstrap" in order: + raise RuntimeError(f"swarm.py ran bootstrap during import: {order}") + if not order or order[0] != "dotenv": + raise RuntimeError(f"swarm.py did not configure runtime during import: {order}") def smoke_onboard_env_writes() -> None: @@ -141,9 +145,9 @@ def smoke_onboard_env_writes() -> None: def main() -> int: - smoke_swarm_bootstrap_order() + smoke_swarm_import_skips_bootstrap() smoke_onboard_env_writes() - print("OpenSwarm bootstrap and onboarding smoke passed") + print("OpenSwarm import bootstrap and onboarding smoke passed") return 0 diff --git a/swarm.py b/swarm.py index 9ce5e2b..4a026dd 100644 --- a/swarm.py +++ b/swarm.py @@ -1,70 +1,50 @@ import os -from pathlib import Path +from run_utils import _bootstrap, _preload_agentswarm_bin -def _preload_agentswarm_bin() -> None: - # Bootstrap may install python-dotenv, so preserve this one override with stdlib. - if "AGENTSWARM_BIN" in os.environ: - return +_RUNTIME_CONFIGURED = False - try: - lines = ( - (Path(__file__).resolve().parent / ".env") - .read_text(encoding="utf-8") - .splitlines() - ) - except OSError: - return - for line in lines: - value = line.strip() - if not value or value.startswith("#"): - continue - if value.startswith("export "): - value = value.removeprefix("export ").lstrip() - - key, sep, raw = value.partition("=") - if sep != "=" or key.strip() != "AGENTSWARM_BIN": - continue - - raw = raw.strip() - if raw[:1] in {"'", '"'}: - quote = raw[0] - end = raw.find(quote, 1) - raw = raw[1:end] if end != -1 else raw[1:] - else: - raw = raw.split(" #", 1)[0].strip() - os.environ["AGENTSWARM_BIN"] = raw +def _configure_runtime() -> None: + global _RUNTIME_CONFIGURED + if _RUNTIME_CONFIGURED: return + from dotenv import load_dotenv + from agents import set_tracing_disabled, set_tracing_export_api_key + from patches.patch_agency_swarm_dual_comms import apply_dual_comms_patch + from patches.patch_file_attachment_refs import apply_file_attachment_reference_patch + from patches.patch_ipython_interpreter_composio import ( + apply_ipython_composio_context_patch, + ) + from patches.patch_utf8_file_reads import apply_utf8_file_read_patch + + load_dotenv() -from run_utils import _bootstrap + apply_utf8_file_read_patch() + apply_dual_comms_patch() + apply_file_attachment_reference_patch() + apply_ipython_composio_context_patch() -_preload_agentswarm_bin() -_bootstrap() + _tracing_key = os.getenv("OPENAI_API_KEY") + if _tracing_key: + set_tracing_export_api_key(_tracing_key) + else: + set_tracing_disabled(True) -from dotenv import load_dotenv -from agents import set_tracing_disabled, set_tracing_export_api_key -from patches.patch_agency_swarm_dual_comms import apply_dual_comms_patch -from patches.patch_file_attachment_refs import apply_file_attachment_reference_patch -from patches.patch_ipython_interpreter_composio import apply_ipython_composio_context_patch -from patches.patch_utf8_file_reads import apply_utf8_file_read_patch + _RUNTIME_CONFIGURED = True -load_dotenv() -apply_utf8_file_read_patch() -apply_dual_comms_patch() -apply_file_attachment_reference_patch() -apply_ipython_composio_context_patch() +if __name__ == "__main__": + _preload_agentswarm_bin() + _bootstrap() -_tracing_key = os.getenv("OPENAI_API_KEY") -if _tracing_key: - set_tracing_export_api_key(_tracing_key) -else: - set_tracing_disabled(True) +_configure_runtime() def create_agency(load_threads_callback=None): + _configure_runtime() + from agency_swarm import Agency from agency_swarm.tools import Handoff, SendMessage @@ -120,6 +100,11 @@ def create_agency(load_threads_callback=None): return agency -if __name__ == "__main__": + +def _main() -> None: agency = create_agency() agency.tui(show_reasoning=True, reload=False) + + +if __name__ == "__main__": + _main() From 29118bb1e36f0c5e8841a85f967010b74199d308 Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 04:10:04 +0100 Subject: [PATCH 24/25] fix: preserve musl launcher fallback - use installed AgentSwarm binary on musl Linux when no OpenSwarm TUI asset exists - keep explicit TUI URL and glibc validation behavior strict - add launcher smoke coverage for platform resolution --- .github/workflows/test-mac.yml | 3 + bin/openswarm | 31 +++++- scripts/smoke-openswarm-launcher.js | 155 ++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 scripts/smoke-openswarm-launcher.js diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 9fd44e7..37cf669 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -7,6 +7,7 @@ on: - '.github/workflows/test-mac.yml' - 'bin/openswarm' - 'package.json' + - 'scripts/smoke-openswarm-launcher.js' jobs: test: @@ -16,6 +17,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + - name: Smoke-test launcher platform resolution + run: node scripts/smoke-openswarm-launcher.js - uses: actions/setup-python@v5 with: python-version: '3.12' diff --git a/bin/openswarm b/bin/openswarm index 8d544df..efff06f 100755 --- a/bin/openswarm +++ b/bin/openswarm @@ -78,8 +78,35 @@ function getBinaryNames() { return names } +function hasExplicitTuiUrl() { + return Boolean(process.env.OPENSWARM_TUI_URL && process.env.OPENSWARM_TUI_URL.trim()) +} + +function isMuslLinux() { + if (process.platform !== 'linux') return false + + try { + if (fs.existsSync('/etc/alpine-release')) return true + } catch {} + + try { + const result = spawnSync('ldd', ['--version'], { + encoding: 'utf8', + timeout: 1500, + }) + const output = `${result.stdout || ''}\n${result.stderr || ''}`.toLowerCase() + return output.includes('musl') + } catch { + return false + } +} + +function shouldUseDependencyBinary() { + return isMuslLinux() && !hasExplicitTuiUrl() +} + function getDownloadUrl(binaryName) { - const explicit = process.env.OPENSWARM_TUI_URL && process.env.OPENSWARM_TUI_URL.trim() + const explicit = hasExplicitTuiUrl() ? process.env.OPENSWARM_TUI_URL.trim() : '' if (explicit) return explicit return `https://github.com/VRSEN/OpenSwarm/releases/download/v${packageJson.version}/${binaryName}` } @@ -183,6 +210,8 @@ async function download(url, destination) { } async function ensureCustomBinary() { + if (shouldUseDependencyBinary()) return null + const binaryNames = getBinaryNames() for (const binaryName of binaryNames) { const existing = resolveCustomBinary(path.join(packageDir, binaryName)) diff --git a/scripts/smoke-openswarm-launcher.js b/scripts/smoke-openswarm-launcher.js new file mode 100644 index 0000000..1b0b294 --- /dev/null +++ b/scripts/smoke-openswarm-launcher.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node +'use strict' + +const assert = require('assert') +const EventEmitter = require('events') +const fs = require('fs') +const path = require('path') +const vm = require('vm') + +const root = path.dirname(__dirname) +const launcher = path.join(root, 'bin', 'openswarm') + +function createClient(requests) { + return { + get(url, _opts, callback) { + requests.push(url) + const req = new EventEmitter() + req.setTimeout = () => req + req.destroy = (error) => req.emit('error', error) + + process.nextTick(() => { + const res = new EventEmitter() + res.statusCode = 404 + res.headers = {} + res.resume = () => {} + callback(res) + }) + + return req + }, + } +} + +function load(opts) { + const requests = [] + const child = { + spawnSync(command) { + if (command === 'ldd') { + return { + status: 0, + stdout: opts.ldd || '', + stderr: '', + } + } + return { + status: 1, + stdout: '', + stderr: '', + } + }, + } + const files = { + ...fs, + existsSync(target) { + if (target === '/etc/alpine-release') return Boolean(opts.alpine) + return fs.existsSync(target) + }, + } + const proc = { + ...process, + arch: () => opts.arch, + env: { + ...process.env, + ...opts.env, + }, + platform: opts.platform, + stdout: { + write() {}, + }, + } + const modules = { + assert, + events: EventEmitter, + fs: files, + http: createClient(requests), + https: createClient(requests), + os: { + arch: () => opts.arch, + }, + path, + 'child_process': child, + } + const context = { + Buffer, + Error, + JSON, + Promise, + URL, + console, + module: { exports: {} }, + process: proc, + require(name) { + if (modules[name]) return modules[name] + return require(name) + }, + __filename: launcher, + } + context.exports = context.module.exports + + const source = fs.readFileSync(launcher, 'utf8') + const start = source.startsWith('#!') ? source.indexOf('\n') + 1 : 0 + const main = source.lastIndexOf('\nmain().catch') + assert.notEqual(main, -1, 'launcher main call not found') + const script = new vm.Script( + `${source.slice(start, main)} +module.exports = { getBinaryNames, isMuslLinux, shouldUseDependencyBinary, ensureCustomBinary }`, + { filename: launcher }, + ) + script.runInNewContext(context) + + return { + api: context.module.exports, + requests, + } +} + +async function main() { + const musl = load({ + platform: 'linux', + arch: 'x64', + ldd: 'musl libc (x86_64)', + }) + assert.equal(musl.api.isMuslLinux(), true) + assert.equal(musl.api.shouldUseDependencyBinary(), true) + assert.equal(await musl.api.ensureCustomBinary(), null) + assert.deepEqual(musl.requests, []) + + const explicit = load({ + platform: 'linux', + arch: 'x64', + env: { + OPENSWARM_TUI_URL: 'https://example.test/openswarm', + }, + ldd: 'musl libc (x86_64)', + }) + assert.equal(explicit.api.shouldUseDependencyBinary(), false) + assert.deepEqual(explicit.api.getBinaryNames(), ['agentswarm-linux-x64', 'agentswarm-linux-x64-baseline']) + + const glibc = load({ + platform: 'linux', + arch: 'x64', + ldd: 'ldd (GNU libc)', + }) + assert.equal(glibc.api.isMuslLinux(), false) + assert.equal(glibc.api.shouldUseDependencyBinary(), false) + await assert.rejects(() => glibc.api.ensureCustomBinary(), /custom OpenSwarm TUI unavailable/) + assert.ok(glibc.requests.some((url) => url.includes('agentswarm-linux-x64'))) + + console.log('openswarm launcher smoke passed') +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) From fe032c0793d100c36fc10f7d29fb426942c57fee Mon Sep 17 00:00:00 2001 From: Nick Bobrowski <39348559+nicko-ai@users.noreply.github.com> Date: Sun, 24 May 2026 04:13:38 +0100 Subject: [PATCH 25/25] test: avoid launcher startup in platform smoke - stop the VM-loaded launcher source before dependency resolution - keep platform helper coverage independent of npm install --- scripts/smoke-openswarm-launcher.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/smoke-openswarm-launcher.js b/scripts/smoke-openswarm-launcher.js index 1b0b294..5022c8c 100644 --- a/scripts/smoke-openswarm-launcher.js +++ b/scripts/smoke-openswarm-launcher.js @@ -99,10 +99,10 @@ function load(opts) { const source = fs.readFileSync(launcher, 'utf8') const start = source.startsWith('#!') ? source.indexOf('\n') + 1 : 0 - const main = source.lastIndexOf('\nmain().catch') - assert.notEqual(main, -1, 'launcher main call not found') + const startup = source.indexOf('\nconst agentswarmBin = resolveAgentswarm(packageDir)') + assert.notEqual(startup, -1, 'launcher startup block not found') const script = new vm.Script( - `${source.slice(start, main)} + `${source.slice(start, startup)} module.exports = { getBinaryNames, isMuslLinux, shouldUseDependencyBinary, ensureCustomBinary }`, { filename: launcher }, )