from __future__ import annotations import json import os import subprocess import sys from pathlib import Path import pytest from investibook.core.run9_workspace_bootstrap import ( _BOOTSTRAP_SCRIPT, _DEFAULT_SKILLS, bootstrap_run9_workspace, run9_workspace_bootstrap_environment, ) class FakeRun9Client: def __init__(self) -> None: self.kwargs = None async def run_command(self, **kwargs): self.kwargs = kwargs return b"run9-bootstrap-ok\n" @pytest.mark.asyncio async def test_bootstrap_uses_baked_agentkit_and_pi_provider_config(): client = FakeRun9Client() await bootstrap_run9_workspace( client=client, # type: ignore[arg-type] box_id="box-1", workspace_id="ws-1", runtime_name="pi", workspace_root="/workspace", agentkit_root="/opt/investibook/agentkit", pi_config_path="/etc/investibook/pi-core.json", pi_config_payload={"providers": {"amazon-bedrock": {"models": []}}}, pull_wait_ms=100, ) assert client.kwargs["box_id"] == "box-1" assert client.kwargs["command"][:2] == ["python3", "-c"] env = client.kwargs["env_overrides"] assert env["INVESTIBOOK_WORKSPACE_ID"] == "ws-1" assert env["INVESTIBOOK_RUNTIME_NAME"] == "pi" assert env["INVESTIBOOK_AGENTKIT_ROOT"] == "/opt/investibook/agentkit" assert env["INVESTIBOOK_BOOTSTRAP_SCRIPT"] == _BOOTSTRAP_SCRIPT assert json.loads(env["INVESTIBOOK_PI_CONFIG_JSON"])["providers"] == { "amazon-bedrock": {"models": []} } @pytest.mark.asyncio async def test_bootstrap_rejects_unexpected_command_output(): class UnexpectedClient(FakeRun9Client): async def run_command(self, **kwargs): return b"partial" with pytest.raises(RuntimeError, match="unexpected output"): await bootstrap_run9_workspace( client=UnexpectedClient(), # type: ignore[arg-type] box_id="box-1", workspace_id="ws-1", runtime_name="pi", workspace_root="/workspace", agentkit_root="/opt/investibook/agentkit", pi_config_path="/etc/investibook/pi-core.json", pi_config_payload={}, pull_wait_ms=100, ) def test_bootstrap_prevents_opencode_branch_package_install(tmp_path: Path): workspace = tmp_path / "workspace" agentkit = tmp_path / "agentkit" global_config = tmp_path / "global-opencode" config = agentkit / "config" (config / "investibook").mkdir(parents=True) (config / "opencode.json.template").write_text("{}\n", encoding="utf-8") (config / "SKILL_CATALOG.md").write_text("catalog\n", encoding="utf-8") for skill in _DEFAULT_SKILLS: skill_dir = agentkit / "skills" / "builtin" / skill skill_dir.mkdir(parents=True) (skill_dir / "SKILL.md").write_text(f"# {skill}\n", encoding="utf-8") (agentkit / "plugins").mkdir() env = os.environ.copy() env.update( { "INVESTIBOOK_WORKSPACE_ID": "ws-test", "INVESTIBOOK_RUNTIME_NAME": "opencode", "INVESTIBOOK_WORKSPACE_ROOT": str(workspace), "INVESTIBOOK_AGENTKIT_ROOT": str(agentkit), "INVESTIBOOK_PI_CONFIG_PATH": str(tmp_path / "pi-core.json"), "INVESTIBOOK_PI_CONFIG_JSON": "{}", "INVESTIBOOK_DEFAULT_SKILLS": json.dumps(_DEFAULT_SKILLS), "INVESTIBOOK_OPENCODE_GLOBAL_CONFIG_DIR": str(global_config), } ) result = subprocess.run( [sys.executable, "-c", _BOOTSTRAP_SCRIPT], check=True, capture_output=True, text=True, env=env, ) assert result.stdout.strip() == "run9-bootstrap-ok" for config_dir in (workspace / ".opencode", global_config): package = json.loads((config_dir / "package.json").read_text()) lock = json.loads((config_dir / "package-lock.json").read_text()) assert package["dependencies"]["@opencode-ai/plugin"] == "*" assert lock["packages"][""]["dependencies"]["@opencode-ai/plugin"] == "*" assert (config_dir / "node_modules").is_dir() def test_opencode_bootstrap_prefers_bundled_plugin(tmp_path: Path): workspace = tmp_path / "workspace" agentkit = tmp_path / "agentkit" config = agentkit / "config" (config / "investibook").mkdir(parents=True) (config / "opencode.json.template").write_text("{}\n", encoding="utf-8") (config / "SKILL_CATALOG.md").write_text("catalog\n", encoding="utf-8") for skill in _DEFAULT_SKILLS: skill_dir = agentkit / "skills" / "builtin" / skill skill_dir.mkdir(parents=True) (skill_dir / "SKILL.md").write_text(f"# {skill}\n", encoding="utf-8") plugin = agentkit / "plugins" / "telemetry" (plugin / "dist").mkdir(parents=True) (plugin / "dist" / "index.js").write_text("export default {};\n") (plugin / "index.ts").write_text("throw new Error('source loaded');\n") env = os.environ.copy() env.update( { "INVESTIBOOK_WORKSPACE_ID": "ws-test", "INVESTIBOOK_RUNTIME_NAME": "opencode", "INVESTIBOOK_WORKSPACE_ROOT": str(workspace), "INVESTIBOOK_AGENTKIT_ROOT": str(agentkit), "INVESTIBOOK_PI_CONFIG_PATH": str(tmp_path / "pi-core.json"), "INVESTIBOOK_PI_CONFIG_JSON": "{}", "INVESTIBOOK_DEFAULT_SKILLS": json.dumps(_DEFAULT_SKILLS), "INVESTIBOOK_OPENCODE_GLOBAL_CONFIG_DIR": str(tmp_path / "global"), } ) subprocess.run([sys.executable, "-c", _BOOTSTRAP_SCRIPT], check=True, env=env) installed = workspace / ".opencode" / "plugins" / "telemetry.js" assert installed.is_symlink() assert installed.resolve() == plugin / "dist" / "index.js" assert not (workspace / ".opencode" / "plugins" / "telemetry.ts").exists() def test_pi_bootstrap_installs_shared_agentkit_assets_without_opencode_runtime( tmp_path: Path, ): workspace = tmp_path / "workspace" agentkit = tmp_path / "agentkit" config = agentkit / "config" (config / "investibook" / "agents").mkdir(parents=True) (config / "investibook" / "agents" / "investigator.md").write_text( "investigator\n", encoding="utf-8" ) (config / "SKILL_CATALOG.md").write_text("catalog\n", encoding="utf-8") for skill in _DEFAULT_SKILLS: skill_dir = agentkit / "skills" / "builtin" / skill skill_dir.mkdir(parents=True) (skill_dir / "SKILL.md").write_text(f"# {skill}\n", encoding="utf-8") env = os.environ.copy() env.update( { "INVESTIBOOK_WORKSPACE_ID": "ws-test", "INVESTIBOOK_RUNTIME_NAME": "pi", "INVESTIBOOK_WORKSPACE_ROOT": str(workspace), "INVESTIBOOK_AGENTKIT_ROOT": str(agentkit), "INVESTIBOOK_PI_CONFIG_PATH": str(tmp_path / "pi-core.json"), "INVESTIBOOK_PI_CONFIG_JSON": "{}", "INVESTIBOOK_DEFAULT_SKILLS": json.dumps(_DEFAULT_SKILLS), "INVESTIBOOK_OPENCODE_GLOBAL_CONFIG_DIR": str(tmp_path / "global-opencode"), } ) subprocess.run([sys.executable, "-c", _BOOTSTRAP_SCRIPT], check=True, env=env) assert (workspace / ".pi" / "settings.json").is_file() assert (workspace / ".opencode" / "investibook" / "agents" / "investigator.md").is_file() assert (workspace / ".opencode" / "investibook" / "SKILL_CATALOG.md").is_file() assert (workspace / ".opencode" / ".all-skills").is_symlink() for skill in _DEFAULT_SKILLS: assert (workspace / ".opencode" / "skills" / "builtin" / skill / "SKILL.md").is_file() assert (workspace / ".pi" / "skills" / skill / "SKILL.md").is_file() assert not (workspace / ".opencode" / "opencode.json").exists() assert not (workspace / ".opencode" / "plugins").exists() assert not (workspace / ".opencode" / "package.json").exists() assert not (tmp_path / "global-opencode").exists() def test_bootstrap_environment_contains_embedded_script(): env = run9_workspace_bootstrap_environment( workspace_id="ws-1", runtime_name="pi", workspace_root="/workspace", agentkit_root="/agentkit", pi_config_path="/tmp/pi.json", pi_config_payload={}, ) assert env["INVESTIBOOK_BOOTSTRAP_SCRIPT"] == _BOOTSTRAP_SCRIPT