mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(grok): start the in-container SDK server + budget feed (Claude parity)
The keystone of the Grok parity work (CEO's "take Claude as baseline, create what's missing" call): the one-shot Grok container now starts the same SDK server the Claude path runs, so the per-verb circuit breaker (the flow/do MCP servers already POST /verb/attempted to it), the per-session budget/loop counters, the terminal-verb tracking, and the SessionEnd post-mortem all work on Grok instead of being silently absent. - entrypoint: launch roboco.agent_sdk.server (bare venv python, not `uv run` which would re-sync the drifted clone lock and stall), wait for /health, reset counters; run opencode WITHOUT exec so the script regains control to run the post-mortem and the silent-exit substitute after the run returns. - budget-feed.js: opencode plugin that gates on /budget/status in tool.execute.before (halt/loop deny — the only place to stop a runaway one-shot run; opencode has no PostToolUse-deny) and records the executed tool + args-hash in tool.execute.after. Fail-open; bare-verb normalization for MCP-namespaced terminal verbs. - silent-exit substitute: on a graceful exit with no terminal verb the entrypoint posts /terminal/force_substitute so the task isn't left stuck claimed/in_progress (Stop-hook parity at the boundary). - opencode_config: wire budget-feed into the plugin array; add ROBOCO_OPENCODE_EXTRA_PLUGINS so per-image role tool plugins load scoped to one role; read the per-role ROBOCO_GROK_EDIT_PERMISSION. Targeted gate green (ruff/mypy/xenon + opencode_config tests; node --check on the plugins; bash -n on the entrypoint).
This commit is contained in:
@@ -11,6 +11,7 @@ from roboco.llm.providers.opencode_config import (
|
||||
OpencodeGuards,
|
||||
XaiTarget,
|
||||
_env_int,
|
||||
_extra_plugins,
|
||||
build_opencode_config,
|
||||
translate_mcp_servers,
|
||||
)
|
||||
@@ -88,8 +89,52 @@ def test_build_opencode_config_provider_and_model() -> None:
|
||||
# Gateway servers carried through.
|
||||
assert "roboco-flow" in cfg["mcp"]
|
||||
assert cfg["instructions"] == ["/app/system-prompt.md"]
|
||||
# The secret-scrub command guard is wired in by default.
|
||||
assert cfg["plugin"] == ["/app/opencode-plugins/secret-scrub.js"]
|
||||
# The secret-scrub command guard + the SDK budget-feed are wired in by default.
|
||||
assert cfg["plugin"] == [
|
||||
"/app/opencode-plugins/secret-scrub.js",
|
||||
"/app/opencode-plugins/budget-feed.js",
|
||||
]
|
||||
|
||||
|
||||
def test_build_opencode_config_appends_extra_plugins() -> None:
|
||||
# Per-image role tool plugins (secretary directive tools, intake propose_draft)
|
||||
# append AFTER the baked defaults so the role-scoped tools load too.
|
||||
cfg = build_opencode_config(
|
||||
_MCP,
|
||||
_TARGET,
|
||||
instruction_paths=[],
|
||||
extra_plugins=["/app/opencode-plugins/secretary-tools.js"],
|
||||
)
|
||||
assert cfg["plugin"] == [
|
||||
"/app/opencode-plugins/secret-scrub.js",
|
||||
"/app/opencode-plugins/budget-feed.js",
|
||||
"/app/opencode-plugins/secretary-tools.js",
|
||||
]
|
||||
|
||||
|
||||
def test_extra_plugins_reads_pathsep_env() -> None:
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
assert _extra_plugins() == []
|
||||
joined = os.pathsep.join(["/a/one.js", "/b/two.js"])
|
||||
with patch.dict(os.environ, {"ROBOCO_OPENCODE_EXTRA_PLUGINS": joined}):
|
||||
assert _extra_plugins() == ["/a/one.js", "/b/two.js"]
|
||||
# Blank entries are dropped (a trailing pathsep or empty override is benign).
|
||||
with patch.dict(
|
||||
os.environ, {"ROBOCO_OPENCODE_EXTRA_PLUGINS": f"/a/one.js{os.pathsep} "}
|
||||
):
|
||||
assert _extra_plugins() == ["/a/one.js"]
|
||||
|
||||
|
||||
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(
|
||||
{},
|
||||
_TARGET,
|
||||
instruction_paths=[],
|
||||
guards=OpencodeGuards(edit_permission="deny"),
|
||||
)
|
||||
assert cfg["permission"]["edit"] == "deny"
|
||||
|
||||
|
||||
def test_build_opencode_config_bash_permission_is_tunable() -> None:
|
||||
|
||||
Reference in New Issue
Block a user