feat(agent): add Grok Build TUI adapter, runtime dispatch, and skill support
This commit is contained in:
@@ -26,11 +26,18 @@ def test_resolve_home_contract():
|
||||
assert resolve_home() == home_val
|
||||
|
||||
def test_agent_adapter_registry():
|
||||
for agent in ('claude', 'agy', 'hermes', 'cline'):
|
||||
EXPECTED_OWN_KEYS = {
|
||||
'claude': 'claude_session_id_own',
|
||||
'agy': 'agy_conversation_id_own',
|
||||
'hermes': 'hermes_conversation_id_own',
|
||||
'cline': 'cline_conversation_id_own',
|
||||
'grok': 'grok_session_id_own',
|
||||
}
|
||||
for agent, expected in EXPECTED_OWN_KEYS.items():
|
||||
adapter = get_adapter(agent)
|
||||
assert adapter is not None
|
||||
assert adapter.name == agent
|
||||
assert adapter.own_key == f"{agent}_{'session' if agent == 'claude' else 'conversation'}_id_own"
|
||||
assert adapter.own_key == expected
|
||||
assert own_key(agent) == adapter.own_key
|
||||
|
||||
def test_agent_of_row_priority():
|
||||
@@ -67,6 +74,7 @@ def test_adapter_required_properties():
|
||||
'agy': ('Antigravity', 'Exit', 'antigravity-cli', ('conversation_id', 'conversation_db', 'conversation_brain_dir')),
|
||||
'hermes': ('Hermes', '/exit', 'hermes-agent', ('session_id',)),
|
||||
'cline': ('Cline|history|Chat|What can I do|slash commands', '/exit', 'cline-agent', ('session_id',)),
|
||||
'grok': ('Grok|xAI|Assistant|❯|>>>', '/exit', 'grok-build', ('session_id', 'session_jsonl')),
|
||||
}
|
||||
for agent, (toks, exitk, delk, cache_f) in expected.items():
|
||||
adapter = get_adapter(agent)
|
||||
@@ -82,7 +90,7 @@ def test_facts_bridge_eval_contract():
|
||||
env = os.environ.copy()
|
||||
skills_dir = str(Path(__file__).resolve().parent.parent / ".agents" / "skills")
|
||||
env["PYTHONPATH"] = f"{skills_dir}:{env.get('PYTHONPATH', '')}"
|
||||
for agent in ('claude', 'agy', 'hermes', 'cline'):
|
||||
for agent in ('claude', 'agy', 'hermes', 'cline', 'grok'):
|
||||
res = subprocess.run([sys.executable, "-m", "lib_py.agents", "facts", agent], capture_output=True, text=True, env=env)
|
||||
assert res.returncode == 0
|
||||
facts_output = res.stdout
|
||||
@@ -174,6 +182,19 @@ def test_purge_artifacts_composite(tmp_path):
|
||||
assert len(purged_cl) == 1
|
||||
assert not os.path.exists(cline_dir)
|
||||
|
||||
# 5. Grok (session directory containing chat_history.jsonl)
|
||||
grok_adapter = get_adapter('grok')
|
||||
grok_ctx = DiscoveryContext(workspace=ws, agent_name='grok', home_dir=home)
|
||||
g_path = grok_adapter.artifact_path('uuid-g', grok_ctx)
|
||||
os.makedirs(os.path.dirname(g_path), exist_ok=True)
|
||||
with open(g_path, 'w') as f:
|
||||
f.write('{"session_id": "uuid-g"}')
|
||||
assert os.path.exists(g_path)
|
||||
purged_g = grok_adapter.purge_artifacts('uuid-g', grok_ctx)
|
||||
assert len(purged_g) == 1
|
||||
assert not os.path.exists(g_path)
|
||||
assert not os.path.exists(os.path.dirname(g_path))
|
||||
|
||||
def test_adapter_spawn_and_resume_specs():
|
||||
claude = get_adapter('claude')
|
||||
assert claude.spawn_spec('claude', 'u1') == 'claude --dangerously-skip-permissions --session-id u1'
|
||||
@@ -194,6 +215,12 @@ def test_adapter_spawn_and_resume_specs():
|
||||
assert cline.resume_spec('cline', 'u1', materialized=True) == 'cline -i --id u1'
|
||||
assert cline.resume_spec('cline', 'u1', materialized=False) == 'cline -i'
|
||||
|
||||
grok = get_adapter('grok')
|
||||
assert grok.spawn_spec('grok', 'u1') == 'grok --session-id u1 --permission-mode bypassPermissions'
|
||||
assert grok.spawn_spec('grok', '') == 'grok --permission-mode bypassPermissions'
|
||||
assert grok.resume_spec('grok', 'u1', materialized=True) == 'grok --resume u1 --permission-mode bypassPermissions'
|
||||
assert grok.resume_spec('grok', 'u1', materialized=False) == 'grok --session-id u1 --permission-mode bypassPermissions'
|
||||
|
||||
def test_adapter_auth_ok(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HOME_DIR", str(tmp_path))
|
||||
# Claude auth runner
|
||||
@@ -209,6 +236,17 @@ def test_adapter_auth_ok(tmp_path, monkeypatch):
|
||||
oauth_file.write_text("{}")
|
||||
assert agy.auth_ok() is True
|
||||
|
||||
# Grok auth check (env var or auth.json file)
|
||||
grok = get_adapter('grok')
|
||||
assert grok.auth_ok() is False
|
||||
monkeypatch.setenv("XAI_API_KEY", "test-key")
|
||||
assert grok.auth_ok() is True
|
||||
monkeypatch.delenv("XAI_API_KEY", raising=False)
|
||||
grok_auth = tmp_path / ".grok" / "auth.json"
|
||||
grok_auth.parent.mkdir(parents=True, exist_ok=True)
|
||||
grok_auth.write_text("{}")
|
||||
assert grok.auth_ok() is True
|
||||
|
||||
# Hermes & Cline always True
|
||||
assert get_adapter('hermes').auth_ok() is True
|
||||
assert get_adapter('cline').auth_ok() is True
|
||||
@@ -268,6 +306,15 @@ def test_adapter_discover(tmp_path):
|
||||
f.write('{"session_id": "u-cl1", "cwd": "' + ws + '"}')
|
||||
assert cline.discover(ctx_cl) == ['u-cl1']
|
||||
|
||||
# 5. Grok
|
||||
grok = get_adapter('grok')
|
||||
ctx_g = DiscoveryContext(workspace=ws, agent_name='grok', home_dir=home)
|
||||
g_sess = f"{home}/.grok/sessions/{grok._ws_dir(ctx_g)}/u-g1"
|
||||
os.makedirs(g_sess, exist_ok=True)
|
||||
with open(f"{g_sess}/chat_history.jsonl", 'w') as f:
|
||||
f.write('{"session_id": "u-g1", "cwd": "' + ws + '"}\n')
|
||||
assert grok.discover(ctx_g) == ['u-g1']
|
||||
|
||||
def test_cli_bridge_subcommands_and_quote_safety():
|
||||
import subprocess, sys
|
||||
from pathlib import Path
|
||||
@@ -286,7 +333,7 @@ def test_cli_bridge_subcommands_and_quote_safety():
|
||||
assert res.stdout.strip() == "/bin/claude --dangerously-skip-permissions --session-id uuid-test"
|
||||
|
||||
# 3. exit-key
|
||||
for agent, expected_key in [('claude', '/exit'), ('agy', 'Exit'), ('hermes', '/exit'), ('cline', '/exit')]:
|
||||
for agent, expected_key in [('claude', '/exit'), ('agy', 'Exit'), ('hermes', '/exit'), ('cline', '/exit'), ('grok', '/exit')]:
|
||||
res = subprocess.run([sys.executable, "-m", "lib_py.agents", "exit-key", agent], capture_output=True, text=True, env=env)
|
||||
assert res.returncode == 0
|
||||
assert res.stdout.strip() == expected_key
|
||||
@@ -298,6 +345,7 @@ def test_delegate_agent_resolution_and_fallback():
|
||||
'agy': 'antigravity-cli',
|
||||
'hermes': 'hermes-agent',
|
||||
'cline': 'cline-agent',
|
||||
'grok': 'grok-build',
|
||||
}
|
||||
# 1. Adapter property
|
||||
for agent, expected_key in expected_map.items():
|
||||
@@ -316,6 +364,7 @@ def test_delegate_agent_resolution_and_fallback():
|
||||
hermes) delegate_agent="hermes-agent" ;;
|
||||
cline) delegate_agent="cline-agent" ;;
|
||||
agy) delegate_agent="antigravity-cli" ;;
|
||||
grok) delegate_agent="grok-build" ;;
|
||||
*) echo "ERROR: cannot resolve delegate agent key for '$AGENT'" >&2; exit 2 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user