mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Live verification (opencode 1.17.8 + grok-build-0.1, funded key) showed the Grok runtime was loading INERT, three ways: 1. The provider override `provider.xai.npm=@ai-sdk/openai` failed model resolution (ProviderModelNotFoundError) — opencode can't resolve that package from its module path. Worse, ANY custom `provider.xai` block (even just options) breaks plugin-tool registration. opencode's BUILT-IN xai provider drives grok-build-0.1 with working tool-calls, so emit NO provider block; the key + base reach it via XAI_API_KEY / XAI_BASE_URL env (provider.options.apiKey alone does NOT authenticate). 2. Plugins referenced by absolute path in the config `plugin:` array never registered their hooks/tools. opencode 1.17.8 only registers from the plugin AUTO-DISCOVERY dir (~/.config/opencode/plugin/). Bake all plugins there. 3. Plugins must use a NAMED export, not `export default`. Changes: - opencode_config: no `provider` block, no `plugin` array; drop the dead XaiTarget + timeout machinery; build_opencode_config now takes a model string. - GrokProvider / orchestrator interactive env: inject XAI_API_KEY + XAI_BASE_URL (drop the now-unused OPENAI_*). - secret-scrub / budget-feed / secretary-tools / intake-tools: named exports; baked into /home/agent/.config/opencode/plugin/ (drop the EXTRA_PLUGINS env). - agent-grok* Dockerfiles: plugin dir + agent ownership; drop the unneeded @ai-sdk/openai global install. Verified live end-to-end: grok-build-0.1 calls read_company_state AND submit_directive through secretary-tools.js and the backend receives both with the agent token; a tool.execute.before guard fires; built-in tool-calls work. Targeted gate green (ruff/mypy/xenon + opencode_config/providers/interactive tests; node --check the plugins).
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
"""Tests for the Grok opencode.json generator (RoboCo MCP -> opencode config)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from roboco.llm.providers.opencode_config import (
|
|
OpencodeGuards,
|
|
build_opencode_config,
|
|
translate_mcp_servers,
|
|
)
|
|
|
|
_MODEL = "grok-build-0.1"
|
|
|
|
_MCP = {
|
|
"mcpServers": {
|
|
"roboco-flow": {
|
|
"command": "uv",
|
|
"args": ["run", "--no-sync", "python", "-m", "roboco.mcp.flow_server"],
|
|
"env": {
|
|
"ROBOCO_AGENT_ID": "uuid-1",
|
|
"UV_PROJECT_ENVIRONMENT": "/app/.venv",
|
|
},
|
|
},
|
|
"roboco-do": {
|
|
"command": "uv",
|
|
"args": ["run", "--no-sync", "python", "-m", "roboco.mcp.do_server"],
|
|
"env": {"ROBOCO_AGENT_ID": "uuid-1"},
|
|
},
|
|
}
|
|
}
|
|
|
|
|
|
def test_translate_mcp_servers_shape() -> None:
|
|
out = translate_mcp_servers(_MCP)
|
|
flow = out["roboco-flow"]
|
|
assert flow["type"] == "local"
|
|
assert flow["enabled"] is True
|
|
# command + args collapse into a single command array (opencode shape).
|
|
assert flow["command"] == [
|
|
"uv",
|
|
"run",
|
|
"--no-sync",
|
|
"python",
|
|
"-m",
|
|
"roboco.mcp.flow_server",
|
|
]
|
|
# env -> environment (opencode key).
|
|
assert flow["environment"]["ROBOCO_AGENT_ID"] == "uuid-1"
|
|
assert "env" not in flow
|
|
assert set(out) == {"roboco-flow", "roboco-do"}
|
|
|
|
|
|
def test_translate_mcp_servers_empty() -> None:
|
|
assert translate_mcp_servers({}) == {}
|
|
assert translate_mcp_servers({"mcpServers": {}}) == {}
|
|
|
|
|
|
def test_translate_mcp_servers_omits_environment_when_no_env() -> None:
|
|
out = translate_mcp_servers(
|
|
{"mcpServers": {"x": {"command": "uv", "args": ["run"]}}}
|
|
)
|
|
assert "environment" not in out["x"]
|
|
assert out["x"]["command"] == ["uv", "run"]
|
|
|
|
|
|
def test_build_opencode_config_emits_no_provider_block() -> None:
|
|
cfg = build_opencode_config(
|
|
_MCP,
|
|
_MODEL,
|
|
instruction_paths=["/app/system-prompt.md"],
|
|
)
|
|
# CRITICAL: NO provider block. ANY provider.xai block breaks plugin-tool
|
|
# registration on opencode 1.17.8 (verified live). The built-in xai provider
|
|
# drives the model; the key reaches it via the XAI_API_KEY env var.
|
|
assert "provider" not in cfg
|
|
# Top-level model selector is "<provider>/<model>".
|
|
assert cfg["model"] == "xai/grok-build-0.1"
|
|
# Gateway servers carried through.
|
|
assert "roboco-flow" in cfg["mcp"]
|
|
assert cfg["instructions"] == ["/app/system-prompt.md"]
|
|
|
|
|
|
def test_build_opencode_config_has_no_plugin_array() -> None:
|
|
# opencode 1.17.8 ignores config `plugin:`-array absolute paths for
|
|
# registration; plugins live in the auto-discovery dir, baked into the images.
|
|
cfg = build_opencode_config(_MCP, _MODEL, instruction_paths=[])
|
|
assert "plugin" not in cfg
|
|
|
|
|
|
def test_build_opencode_config_edit_permission_is_tunable() -> None:
|
|
# Read-only roles (qa / pr_reviewer / auditor / PMs / board) get edit=deny so
|
|
# a Grok agent can't write code on a role that must never touch the tree.
|
|
cfg = build_opencode_config(
|
|
{},
|
|
_MODEL,
|
|
instruction_paths=[],
|
|
guards=OpencodeGuards(edit_permission="deny"),
|
|
)
|
|
assert cfg["permission"]["edit"] == "deny"
|
|
|
|
|
|
def test_build_opencode_config_bash_permission_is_tunable() -> None:
|
|
cfg = build_opencode_config(
|
|
{},
|
|
_MODEL,
|
|
instruction_paths=[],
|
|
guards=OpencodeGuards(bash_permission="deny"),
|
|
)
|
|
assert cfg["permission"]["bash"] == "deny"
|
|
assert cfg["permission"]["edit"] == "allow"
|
|
|
|
|
|
def test_build_opencode_config_allows_external_directory_by_default() -> None:
|
|
# opencode auto-denies an "ask" external-dir read in headless mode (the
|
|
# pr-reviewer couldn't read a diff it wrote to /tmp); default "allow".
|
|
cfg = build_opencode_config(_MCP, _MODEL, instruction_paths=[])
|
|
assert cfg["permission"]["external_directory"] == "allow"
|
|
|
|
|
|
def test_build_opencode_config_external_directory_is_tunable() -> None:
|
|
cfg = build_opencode_config(
|
|
{},
|
|
_MODEL,
|
|
instruction_paths=[],
|
|
guards=OpencodeGuards(external_directory_permission="deny"),
|
|
)
|
|
assert cfg["permission"]["external_directory"] == "deny"
|
|
|
|
|
|
def test_build_opencode_config_disables_subagent_task_tool_by_default() -> None:
|
|
# The subagent `task` tool must be hard-disabled: a RoboCo role never uses
|
|
# opencode-internal subagents, and one spawned on grok-build-0.1 hung the run.
|
|
cfg = build_opencode_config(_MCP, _MODEL, instruction_paths=[])
|
|
assert cfg["tools"] == {"task": False}
|
|
|
|
|
|
def test_build_opencode_config_subagents_can_be_re_enabled() -> None:
|
|
cfg = build_opencode_config(
|
|
_MCP,
|
|
_MODEL,
|
|
instruction_paths=[],
|
|
guards=OpencodeGuards(disable_subagents=False),
|
|
)
|
|
assert "tools" not in cfg
|