File size: 12,005 Bytes
69f9cd1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
"""Naldo_Bot on OpenCode only: offline unit tests.

No network, no model, no Minecraft. Pure stdlib plus text-level assertions
on the deployment files.
"""

from __future__ import annotations

import ast
import sys
from pathlib import Path
from types import SimpleNamespace
from unittest import mock

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

import naldo_opencode as brain  # noqa: E402
import naldo_opencode_bridge as bridge_module  # noqa: E402


def test_primary_and_fallbacks_are_all_opencode_free():
    assert brain.OPENCODE_PROVIDER == "opencode"
    assert brain.PRIMARY_MODEL == "muse-spark-1.3-contributor-free"
    assert brain.FALLBACK_MODELS[0] == brain.PRIMARY_MODEL
    assert set(brain.FALLBACK_MODELS) == {
        "muse-spark-1.3-contributor-free",
        "fledge-alpha-free",
        "nemotron-3-ultra-free",
    }
    for model in brain.FALLBACK_MODELS:
        assert brain.is_allowed_model(model)
        assert brain.is_allowed_model(f"opencode/{model}")
        lowered = model.lower()
        for forbidden in ("granite", "gemini", "gpt", "claude", "anthropic", "qwen"):
            assert forbidden not in lowered


def test_no_granite_gemini_fallback_anywhere():
    for name in ("granite", "gemini", "gpt-", "anthropic", "claude"):
        assert name not in {m.lower() for m in brain.FALLBACK_MODELS}
    stack = (ROOT / "voyager_stack.py").read_text(encoding="utf-8")
    assert "granite-4.0-h-tiny" not in stack, "Naldo stack must not reference Granite anymore"
    bridge = (ROOT / "naldo_opencode_bridge.py").read_text(encoding="utf-8")
    lowered = bridge.lower()
    for forbidden in ("granite", "gemini", "anthropic"):
        assert forbidden not in lowered, f"bridge leaked {forbidden}"
    runner = (ROOT / "voyager_runner.py").read_text(encoding="utf-8")
    # Gemini_Bot's own lock stays; Naldo's lock must not accept granite.
    assert "naldo-granite" not in runner


def test_brain_lock_requires_opencode_profile_base_and_models():
    runner = (ROOT / "voyager_runner.py").read_text(encoding="utf-8")
    assert "NALDO_BRAIN_PROFILE" in runner or "naldo-opencode" in runner
    assert "http://127.0.0.1:7864/v1" in runner
    assert "muse-spark-1.3-contributor-free" in runner
    assert "_naldo_model_allowed" in runner
    # Rejected models: granite/gemini must not pass the predicate.
    assert not brain.is_allowed_model("granite-4.0-h-tiny")
    assert not brain.is_allowed_model("gemini-3.5-flash-lite")
    assert not brain.is_allowed_model("gpt-4o")
    assert brain.is_allowed_model("fledge-alpha-free")
    assert brain.is_allowed_model("nemotron-3-ultra-free")


def test_bridge_formats_messages_without_confusing_roles():
    messages = [
        {"role": "system", "content": "You are a planner."},
        {"role": "user", "content": "Do X"},
        {"role": "assistant", "content": "Plan Y"},
        {"role": "tool", "content": "tool output Z"},
        {"role": "user", "content": "Now?", "tool_calls": [{"id": "1", "function": {"name": "f", "arguments": "{}"}}]},
    ]
    rendered = brain.build_opencode_prompt(messages)
    assert "[SYSTEM]" in rendered["prompt"]
    assert "[USER]" in rendered["prompt"]
    assert "[ASSISTANT]" in rendered["prompt"]
    assert "[TOOL]" in rendered["prompt"]
    assert "You are a planner." in rendered["system"]
    assert "TOOL_CALLS" in rendered["prompt"]
    assert "text generator, not a file/shell executor" in rendered["prompt"]


def test_bridge_extracts_only_text_parts():
    payload = {
        "info": {"role": "assistant"},
        "parts": [
            {"type": "reasoning", "thinking": "secret plan"},
            {"type": "text", "text": "hello "},
            {"type": "tool", "name": "bash", "input": "rm -rf /"},
            {"type": "text", "text": "world"},
        ],
    }
    assert brain.extract_assistant_text(payload) == "hello world"
    assert brain.extract_assistant_text({"parts": []}) == ""
    assert brain.extract_assistant_text({}) == ""


def test_sessions_use_free_tier_compatible_tool_sandbox(tmp_path):
    captured = {}

    def fake_http(url, method="GET", body=None, timeout=0, retries=0):
        captured.update({"url": url, "method": method, "body": body})
        return {"id": "ses_test"}

    with mock.patch.object(bridge_module, "OPENCODE_SESSION_DIR", tmp_path), \
         mock.patch.object(bridge_module, "_http_json", side_effect=fake_http):
        session_id = bridge_module._create_session("muse-spark-1.3-contributor-free")

    assert session_id == "ses_test"
    assert captured["method"] == "POST"
    assert captured["url"].endswith("/session")
    assert captured["body"]["model"] == {
        "id": "muse-spark-1.3-contributor-free",
        "providerID": "opencode",
    }
    assert captured["body"]["location"] == {"directory": str(tmp_path)}
    assert "permission" not in captured["body"], "custom permissions trigger Zen free-tier 403"

    message = brain.opencode_message_body("muse-spark-1.3-contributor-free", "sys", "do X")
    assert message["tools"] == {}, "empty tools map is the verified free-tier tool sandbox"
    assert "temperature" not in message


def test_loopback_only():
    assert brain.is_loopback_url(brain.OPENCODE_BASE)
    assert brain.is_loopback_url(brain.BRIDGE_BASE)
    assert "0.0.0.0" not in (ROOT / "naldo_opencode_bridge.py").read_text(encoding="utf-8")
    stack = (ROOT / "voyager_stack.py").read_text(encoding="utf-8")
    assert "opencode_serve_command(opencode_binary" in stack
    assert 'host="127.0.0.1"' in (ROOT / "naldo_opencode_bridge.py").read_text(encoding="utf-8")
    assert "shell=True" not in (ROOT / "naldo_opencode_bridge.py").read_text(encoding="utf-8")
    assert "shell=True" not in (ROOT / "naldo_opencode.py").read_text(encoding="utf-8")
    serve = brain.opencode_serve_command("/tmp/opencode-local", 7865)
    assert serve[:3] == ["/tmp/opencode-local", "serve", "--hostname"]
    assert "127.0.0.1" in serve
    assert "0.0.0.0" not in serve


def test_no_credential_dependency_or_leak(tmp_path):
    bridge_src = (ROOT / "naldo_opencode_bridge.py").read_text(encoding="utf-8")
    assert "auth.json" not in bridge_src
    assert 'os.environ.get("OPENCODE_API_KEY"' not in bridge_src
    assert 'os.environ["OPENCODE_API_KEY"]' not in bridge_src

    base = {"OPENCODE_API_KEY": "must-not-survive", "PATH": "x"}
    env = brain.opencode_subprocess_env(base, tmp_path / "home")
    assert "OPENCODE_API_KEY" not in env
    assert env["PATH"] == "x"
    assert env["HOME"] == str(tmp_path / "home")
    assert env["XDG_CONFIG_HOME"] == str(tmp_path / "home" / "config")
    assert env["XDG_DATA_HOME"] == str(tmp_path / "home" / "data")
    assert base["OPENCODE_API_KEY"] == "must-not-survive", "parent env must not be mutated"
    assert "TEMPERATURE_NOTE" in (ROOT / "naldo_opencode.py").read_text(encoding="utf-8")


def test_local_opencode_install_is_pinned_and_non_root(tmp_path):
    calls = []

    def fake_run(args, **kwargs):
        calls.append(list(args))
        return SimpleNamespace(returncode=0, stdout="installed")

    with mock.patch.object(brain, "_probe_opencode", side_effect=["", brain.OPENCODE_VERSION]), \
         mock.patch.object(brain.subprocess, "run", side_effect=fake_run):
        binary, version = brain.ensure_opencode_installed(tmp_path / "cli")

    assert version == brain.OPENCODE_VERSION
    assert Path(binary) == brain.opencode_binary_path(tmp_path / "cli")
    install = calls[0]
    assert "-g" not in install and "--global" not in install
    assert "--prefix" in install
    assert install[install.index("--prefix") + 1] == str(tmp_path / "cli")
    assert brain.OPENCODE_NPM_SPEC in install


def test_max_tokens_is_telemetry_not_output_truncation():
    long_answer = "function build() { return 1; }\n" * 300
    with mock.patch.object(bridge_module, "_create_session", return_value="ses_x"), \
         mock.patch.object(bridge_module, "_send_message", return_value=long_answer):
        result = bridge_module._chat_payload({
            "model": brain.PRIMARY_MODEL,
            "messages": [{"role": "user", "content": "write program"}],
            "temperature": 0,
            "max_tokens": 1,
        })
    assert result["choices"][0]["message"]["content"] == long_answer
    assert bridge_module.state["last_requested_max_tokens"] == 1


def test_supervisor_start_stop_contract():
    stack_src = (ROOT / "voyager_stack.py").read_text(encoding="utf-8")
    for marker in (
        "_ensure_naldo_brain",
        "_brain_healthy",
        "_stop_brain",
        "brain_status",
        "_terminate_naldo_child",
        "ensure_opencode_installed",
        "NALDO_BRIDGE_BASE",
        "NALDO_BRAIN_PROFILE",
        "NALDO_PRIMARY_MODEL",
        "opencode_serve_command",
        "opencode_subprocess_env",
    ):
        assert marker in stack_src, f"supervisor marker missing: {marker}"
    assert "without Granite fallback" in stack_src or "without granite" in stack_src.lower()
    tree = ast.parse(stack_src)
    methods = set()
    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef) and node.name == "VoyagerStack":
            for child in node.body:
                if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
                    methods.add(child.name)
    for required in ("_ensure_naldo_brain", "_brain_healthy", "_stop_brain", "brain_status", "stop", "start_async"):
        assert required in methods
    assert "self.stop_event.wait(7)" in stack_src
    assert "OpenCode/bridge died mid-run; stopping Naldo immediately" in stack_src
    assert "opencode_serve_command(opencode_binary" in stack_src

    runner_src = (ROOT / "voyager_runner.py").read_text(encoding="utf-8")
    assert "Naldo OpenCode brain module unavailable" in runner_src
    assert "except Exception as exc" in runner_src


def test_supervisor_stop_terminates_all_children():
    import voyager_stack as stack_module

    stack = stack_module.VoyagerStack()
    naldo = SimpleNamespace(poll=lambda: None, terminate=lambda: setattr(naldo, "t", True), kill=lambda: None, wait=lambda timeout=None: 0)
    bridge = SimpleNamespace(poll=lambda: None, terminate=lambda: setattr(bridge, "t", True), kill=lambda: None, wait=lambda timeout=None: 0)
    opencode = SimpleNamespace(poll=lambda: None, terminate=lambda: setattr(opencode, "t", True), kill=lambda: None, wait=lambda timeout=None: 0)
    stack.proc = naldo  # type: ignore[assignment]
    stack.bridge_proc = bridge  # type: ignore[assignment]
    stack.opencode_proc = opencode  # type: ignore[assignment]
    naldo.pid = 111
    bridge.pid = 222
    opencode.pid = 333
    stack.stop_event.set()
    with mock.patch.object(stack_module, "_wait_tcp", return_value=True):
        with mock.patch.object(stack, "brain_status", return_value={"ok": True}):
            with mock.patch.object(stack, "_mineflayer_status", return_value={"ready": False}):
                status = stack.stop()
    assert getattr(naldo, "t", False) is True
    assert getattr(bridge, "t", False) is True
    assert getattr(opencode, "t", False) is True
    assert status["state"] == "stopped"


def test_gemini_bot_untouched():
    stack_src = (ROOT / "gemini_bot_stack.py").read_text(encoding="utf-8")
    assert "7863" in stack_src
    assert "gemini-3.5-flash-lite" in stack_src
    assert "opencode" not in stack_src.lower()
    assert "7864" not in stack_src
    assert "7865" not in stack_src


if __name__ == "__main__":
    test_primary_and_fallbacks_are_all_opencode_free()
    test_no_granite_gemini_fallback_anywhere()
    test_brain_lock_requires_opencode_profile_base_and_models()
    test_bridge_formats_messages_without_confusing_roles()
    test_bridge_extracts_only_text_parts()
    test_sessions_created_with_denied_permissions()
    test_loopback_only()
    test_no_credential_dependency_or_leak()
    test_supervisor_start_stop_contract()
    test_gemini_bot_untouched()
    print("PASS naldo opencode")