feat(agent): deprecate and completely remove cline agent support
- Delete adapters/cline.py and unregister from registry.py - Remove cline branches from lib.sh and all 8 skill scripts (create, resume, stop, status, reconcile, update_yaml_resumed, resolve_session_id, orc_onboard) - Narrow own-key mapping dictionaries across lib_py core modules to 4 supported agents - Delete cline-exclusive tests and retarget shared fixtures to grok/hermes/claude - Update skills documentation and installation guides (439 passed, 0 failures) - Archive cline deprecation consensus and review reports
This commit is contained in:
+4
-28
@@ -537,8 +537,7 @@ elif cmd1 == "agent":
|
||||
buffer_content = {
|
||||
"claude": "Anthropic Claude Ready",
|
||||
"agy": "Antigravity Ready",
|
||||
"hermes": "Hermes Ready",
|
||||
"cline": "Cline Chat Ready"
|
||||
"hermes": "Hermes Ready"
|
||||
}.get(agent_type, "Ready")
|
||||
|
||||
# Look up cwd from target pane in state if not explicitly passed
|
||||
@@ -577,8 +576,7 @@ elif cmd1 == "agent":
|
||||
own_key_map = {
|
||||
"claude": "claude_session_id_own",
|
||||
"agy": "agy_conversation_id_own",
|
||||
"hermes": "hermes_conversation_id_own",
|
||||
"cline": "cline_conversation_id_own"
|
||||
"hermes": "hermes_conversation_id_own"
|
||||
}
|
||||
own_key = own_key_map.get(agent_type)
|
||||
if own_key:
|
||||
@@ -629,15 +627,6 @@ elif cmd1 == "agent":
|
||||
conn.execute("INSERT OR REPLACE INTO sessions (id, cwd) VALUES (?, ?)", (session_uuid, ws_abs))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
elif agent_type == "cline":
|
||||
clin_base = os.path.join(home_dir, ".cline", "data", "sessions")
|
||||
if "agent_homes" in home_dir:
|
||||
clin_base = os.path.join(home_dir, "sessions")
|
||||
session_dir = os.path.join(clin_base, session_uuid)
|
||||
os.makedirs(session_dir, exist_ok=True)
|
||||
json_file = os.path.join(session_dir, f"{session_uuid}.json")
|
||||
with open(json_file, 'w') as jf:
|
||||
jf.write(json.dumps({"id": session_uuid}))
|
||||
|
||||
state["agents"] = agents
|
||||
save_state()
|
||||
@@ -913,7 +902,7 @@ sys.exit(0)
|
||||
@pytest.fixture
|
||||
def mock_agents(mam_sandbox, monkeypatch):
|
||||
"""
|
||||
Generates mock agent binaries (claude, agy, hermes, cline)
|
||||
Generates mock agent binaries (claude, agy, hermes)
|
||||
and places them in the sandboxed bin folder to satisfy preflight checks.
|
||||
"""
|
||||
tmp_path = mam_sandbox
|
||||
@@ -957,20 +946,7 @@ sys.exit(0)
|
||||
""")
|
||||
hermes_bin.chmod(0o755)
|
||||
|
||||
# 4. cline mock
|
||||
cline_bin = bin_dir / "cline"
|
||||
cline_bin.write_text("""#!/usr/bin/env python3
|
||||
import sys
|
||||
import json
|
||||
args = sys.argv[1:]
|
||||
if len(args) >= 1 and args[0] == "history":
|
||||
print(json.dumps([]))
|
||||
sys.exit(0)
|
||||
sys.exit(0)
|
||||
""")
|
||||
cline_bin.chmod(0o755)
|
||||
|
||||
# 5. uuidgen mock to guarantee isolated creation UUIDs
|
||||
# 4. uuidgen mock to guarantee isolated creation UUIDs
|
||||
uuidgen_bin = bin_dir / "uuidgen"
|
||||
uuidgen_bin.write_text("""#!/usr/bin/env python3
|
||||
import uuid
|
||||
|
||||
@@ -30,7 +30,6 @@ def test_agent_adapter_registry():
|
||||
'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():
|
||||
@@ -50,8 +49,8 @@ def test_agent_of_row_priority():
|
||||
assert agent_of_row(row2) == 'hermes'
|
||||
|
||||
# Priority 3: pane.cmd exact match
|
||||
row3 = {'pane': {'cmd': 'cline'}}
|
||||
assert agent_of_row(row3) == 'cline'
|
||||
row3 = {'pane': {'cmd': 'grok'}}
|
||||
assert agent_of_row(row3) == 'grok'
|
||||
|
||||
def test_agent_of_row_pane_cmd_binary_path_and_failure():
|
||||
# pane.cmd 가 절대 경로 형태여도 해석된다
|
||||
@@ -73,7 +72,6 @@ def test_adapter_required_properties():
|
||||
'claude': ('Anthropic|Assistant|Chat|Welcome|Claude Code|Opus|Sonnet|Haiku', '/exit', 'claude-code', ('session_id', 'session_jsonl', 'session_size_bytes', 'session_lines')),
|
||||
'agy': ('Antigravity', 'Exit', 'antigravity-cli', ('conversation_id', 'conversation_db', 'conversation_brain_dir')),
|
||||
'hermes': ('Hermes|Welcome to Hermes Agent|NOUS 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():
|
||||
@@ -83,13 +81,15 @@ def test_adapter_required_properties():
|
||||
assert adapter.exit_key == exitk
|
||||
assert adapter.delegate_agent_key == delk
|
||||
assert adapter.identity_cache_fields == cache_f
|
||||
# 2-Tier readiness default contract
|
||||
assert adapter.strong_ready_tokens == adapter.ready_tokens
|
||||
assert adapter.weak_ready_tokens == ''
|
||||
|
||||
|
||||
def test_adapter_modal_tokens_contract():
|
||||
"""F-8: Verify adapter-level modal_tokens contract (T-2d)."""
|
||||
expected_modal = {
|
||||
'claude': 'Try the new fullscreen renderer\\?',
|
||||
'cline': 'Select API Provider|Enter API Key|Select an API provider|API Provider|Cline API key',
|
||||
'agy': None,
|
||||
'hermes': None,
|
||||
'grok': None,
|
||||
@@ -106,7 +106,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', 'grok'):
|
||||
for agent in ('claude', 'agy', 'hermes', '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
|
||||
@@ -115,12 +115,12 @@ def test_facts_bridge_eval_contract():
|
||||
bash_cmd = f"""
|
||||
set -euo pipefail
|
||||
eval {shlex_quote(facts_output)}
|
||||
echo "AGENT=$MAM_AGENT_NAME|OWN=$MAM_OWN_KEY|TOK=$MAM_READY_TOKENS|EXIT=$MAM_EXIT_KEY|DEL=$MAM_DELEGATE_AGENT_KEY|PH=$MAM_INPUT_PLACEHOLDER"
|
||||
echo "AGENT=$MAM_AGENT_NAME|OWN=$MAM_OWN_KEY|TOK=$MAM_READY_TOKENS|EXIT=$MAM_EXIT_KEY|DEL=$MAM_DELEGATE_AGENT_KEY|PH=$MAM_INPUT_PLACEHOLDER|STRONG=$MAM_STRONG_READY_TOKENS|WEAK=$MAM_WEAK_READY_TOKENS"
|
||||
"""
|
||||
res_bash = subprocess.run(["bash", "-c", bash_cmd], capture_output=True, text=True)
|
||||
assert res_bash.returncode == 0, f"Bash eval failed for {agent}:\nStdout: {res_bash.stdout}\nStderr: {res_bash.stderr}"
|
||||
if agent == 'cline':
|
||||
assert "PH=Ask anything..." in res_bash.stdout
|
||||
assert f"STRONG={get_adapter(agent).strong_ready_tokens}" in res_bash.stdout
|
||||
assert "WEAK=" in res_bash.stdout
|
||||
|
||||
def shlex_quote(s):
|
||||
import shlex
|
||||
@@ -187,18 +187,7 @@ def test_purge_artifacts_composite(tmp_path):
|
||||
assert conn.execute("SELECT count(*) FROM messages WHERE session_id='uuid-h'").fetchone()[0] == 0
|
||||
conn.close()
|
||||
|
||||
# 4. Cline (sessions dir)
|
||||
cline_adapter = get_adapter('cline')
|
||||
cline_ctx = DiscoveryContext(workspace=ws, agent_name='cline', home_dir=home)
|
||||
cline_dir = f"{home}/.cline/data/sessions/uuid-cl"
|
||||
os.makedirs(cline_dir, exist_ok=True)
|
||||
with open(f"{cline_dir}/uuid-cl.json", 'w') as f:
|
||||
f.write('{"session_id": "uuid-cl"}')
|
||||
purged_cl = cline_adapter.purge_artifacts('uuid-cl', cline_ctx)
|
||||
assert len(purged_cl) == 1
|
||||
assert not os.path.exists(cline_dir)
|
||||
|
||||
# 5. Grok (session directory containing chat_history.jsonl)
|
||||
# 4. 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)
|
||||
@@ -227,11 +216,6 @@ def test_adapter_spawn_and_resume_specs():
|
||||
assert hermes.resume_spec('hermes', 'u1', materialized=True) == 'hermes --resume u1 --no-restore-cwd --yolo --accept-hooks'
|
||||
assert hermes.resume_spec('hermes', 'u1', materialized=False) == 'hermes --yolo --accept-hooks'
|
||||
|
||||
cline = get_adapter('cline')
|
||||
assert cline.spawn_spec('cline', 'u1') == 'cline -i'
|
||||
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'
|
||||
@@ -264,9 +248,8 @@ def test_adapter_auth_ok(tmp_path, monkeypatch):
|
||||
grok_auth.write_text("{}")
|
||||
assert grok.auth_ok() is True
|
||||
|
||||
# Hermes & Cline always True
|
||||
# Hermes always True
|
||||
assert get_adapter('hermes').auth_ok() is True
|
||||
assert get_adapter('cline').auth_ok() is True
|
||||
|
||||
def test_adapter_discover(tmp_path):
|
||||
import sqlite3
|
||||
@@ -314,16 +297,7 @@ def test_adapter_discover(tmp_path):
|
||||
conn.close()
|
||||
assert hermes.discover(ctx_h) == ['u-h1']
|
||||
|
||||
# 4. Cline
|
||||
cline = get_adapter('cline')
|
||||
ctx_cl = DiscoveryContext(workspace=ws, agent_name='cline', home_dir=home)
|
||||
cl_sess = f"{home}/.cline/data/sessions/u-cl1"
|
||||
os.makedirs(cl_sess, exist_ok=True)
|
||||
with open(f"{cl_sess}/u-cl1.json", 'w') as f:
|
||||
f.write('{"session_id": "u-cl1", "cwd": "' + ws + '"}')
|
||||
assert cline.discover(ctx_cl) == ['u-cl1']
|
||||
|
||||
# 5. Grok
|
||||
# 4. 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"
|
||||
@@ -350,7 +324,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'), ('grok', '/exit')]:
|
||||
for agent, expected_key in [('claude', '/exit'), ('agy', 'Exit'), ('hermes', '/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
|
||||
@@ -361,7 +335,6 @@ def test_delegate_agent_resolution_and_fallback():
|
||||
'claude': 'claude-code',
|
||||
'agy': 'antigravity-cli',
|
||||
'hermes': 'hermes-agent',
|
||||
'cline': 'cline-agent',
|
||||
'grok': 'grok-build',
|
||||
}
|
||||
# 1. Adapter property
|
||||
@@ -379,7 +352,6 @@ def test_delegate_agent_resolution_and_fallback():
|
||||
case "$AGENT" in
|
||||
claude) delegate_agent="claude-code" ;;
|
||||
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 ;;
|
||||
|
||||
@@ -144,14 +144,31 @@ if _pane_dialog_open dummy; then echo "DIALOG_OPEN"; else echo "DIALOG_CLOSED";
|
||||
|
||||
def test_c3_strong_token_and_hint_token_readiness_succeeds():
|
||||
"""C-3: Strong ready token + hint token together must detect readiness immediately (rc 0)."""
|
||||
screen = """Cline v3.0.0
|
||||
screen = """MockApp v1.0.0
|
||||
Use arrow keys to select a tool
|
||||
❯
|
||||
"""
|
||||
script = f"""
|
||||
mock_py() {{
|
||||
if [ "$1" = "-m" ] && [ "$2" = "lib_py.agents" ] && [ "$3" = "facts" ]; then
|
||||
echo "MAM_AGENT_NAME='mocktiered'"
|
||||
echo "MAM_OWN_KEY='mock_id_own'"
|
||||
echo "MAM_INPUT_PROMPT='❯'"
|
||||
echo "MAM_INPUT_PLACEHOLDER=''"
|
||||
echo "MAM_INPUT_RULE_PATTERN=''"
|
||||
echo "MAM_READY_TOKENS='MockApp|Use arrow keys|Ready'"
|
||||
echo "MAM_STRONG_READY_TOKENS='MockApp'"
|
||||
echo "MAM_WEAK_READY_TOKENS='Use arrow keys'"
|
||||
echo "MAM_MODAL_TOKENS=''"
|
||||
echo "MAM_EXIT_KEY='/exit'"
|
||||
echo "MAM_DELEGATE_AGENT_KEY='mock-agent'"
|
||||
return 0
|
||||
fi
|
||||
}}
|
||||
_delegate_py_bin() {{ echo "mock_py"; }}
|
||||
_pane_capture() {{ printf '%s' '{screen}'; }}
|
||||
sleep() {{ :; }}
|
||||
wait_for_tui_ready dummy-sess cline
|
||||
wait_for_tui_ready dummy-sess mocktiered
|
||||
"""
|
||||
res = _run_lib_helpers(script)
|
||||
assert res.returncode == 0, res.stderr + res.stdout
|
||||
@@ -170,7 +187,7 @@ sleep() {{ :; }}
|
||||
export MAM_STRONG_READY_TOKENS='NonExistentStrongToken'
|
||||
export MAM_WEAK_READY_TOKENS=''
|
||||
export MAM_INPUT_PLACEHOLDER='Ask anything...'
|
||||
wait_for_tui_ready dummy-sess cline
|
||||
wait_for_tui_ready dummy-sess custom
|
||||
"""
|
||||
res = _run_lib_helpers(script)
|
||||
assert res.returncode == 0, res.stderr + res.stdout
|
||||
@@ -197,7 +214,7 @@ export MAM_INPUT_PROMPT='❯'
|
||||
export MAM_INPUT_RULE_PATTERN='─{{10,}}'
|
||||
_MAM_MODAL_TOKENS='NON_MATCHING_MODAL'
|
||||
rc=0
|
||||
wait_for_tui_ready dummy-sess cline || rc=$?
|
||||
wait_for_tui_ready dummy-sess custom || rc=$?
|
||||
echo "RC=$rc"
|
||||
"""
|
||||
res = _run_lib_helpers(script)
|
||||
@@ -219,7 +236,7 @@ export MAM_INPUT_PLACEHOLDER='NonExistentPlaceholder'
|
||||
export MAM_INPUT_PROMPT='❯'
|
||||
export MAM_INPUT_RULE_PATTERN='─{{10,}}'
|
||||
rc=0
|
||||
wait_for_tui_ready dummy-sess cline || rc=$?
|
||||
wait_for_tui_ready dummy-sess custom || rc=$?
|
||||
echo "RC=$rc"
|
||||
"""
|
||||
res = _run_lib_helpers(script)
|
||||
@@ -247,7 +264,7 @@ export MAM_INPUT_PLACEHOLDER=''
|
||||
export MAM_INPUT_PROMPT='❯'
|
||||
export MAM_INPUT_RULE_PATTERN=''
|
||||
rc=0
|
||||
wait_for_tui_ready dummy-sess cline || rc=$?
|
||||
wait_for_tui_ready dummy-sess custom || rc=$?
|
||||
echo "RC=$rc"
|
||||
"""
|
||||
res1 = _run_lib_helpers(script1)
|
||||
@@ -268,7 +285,7 @@ export MAM_INPUT_PLACEHOLDER=''
|
||||
export MAM_INPUT_PROMPT='❯'
|
||||
export MAM_INPUT_RULE_PATTERN=''
|
||||
rc=0
|
||||
wait_for_tui_ready dummy-sess cline || rc=$?
|
||||
wait_for_tui_ready dummy-sess custom || rc=$?
|
||||
echo "RC=$rc"
|
||||
"""
|
||||
res2 = _run_lib_helpers(script2)
|
||||
@@ -343,7 +360,7 @@ export MAM_INPUT_PLACEHOLDER=''
|
||||
export MAM_INPUT_PROMPT='❯'
|
||||
export MAM_INPUT_RULE_PATTERN=''
|
||||
rc=0
|
||||
wait_for_tui_ready dummy-sess cline || rc=$?
|
||||
wait_for_tui_ready dummy-sess custom || rc=$?
|
||||
echo "RC=$rc"
|
||||
"""
|
||||
res = _run_lib_helpers(script)
|
||||
@@ -368,29 +385,29 @@ echo "DIALOG=$_MAM_DIALOG_TOKENS"
|
||||
def test_c8b_agent_modal_tokens_merged_into_dialog_open():
|
||||
"""C-8b (T-2d / F-8): Per-agent modal_tokens from adapter are merged into _pane_dialog_open and block send_keys_safe."""
|
||||
# 1. Direct _pane_dialog_open test
|
||||
screen = """Please enter your Cline API key to continue:
|
||||
screen = """Try the new fullscreen renderer?
|
||||
"""
|
||||
script1 = f"""
|
||||
_pane_capture() {{ printf '%s' '{screen}'; }}
|
||||
export MAM_MODAL_TOKENS='Cline API key'
|
||||
export MAM_MODAL_TOKENS='Try the new fullscreen renderer\\?'
|
||||
if _pane_dialog_open dummy; then echo "DIALOG_OPEN"; else echo "DIALOG_CLOSED"; fi
|
||||
"""
|
||||
res1 = _run_lib_helpers(script1)
|
||||
assert res1.returncode == 0, res1.stderr + res1.stdout
|
||||
assert "DIALOG_OPEN" in res1.stdout
|
||||
|
||||
# 2. Dynamic facts resolution via send_keys_safe on cline session
|
||||
# 2. Dynamic facts resolution via send_keys_safe on claude session
|
||||
script2 = f"""
|
||||
_pane_quiescent() {{ return 0; }}
|
||||
_pane_capture() {{ printf '%s' 'Select an API provider to get started:'; }}
|
||||
_pane_capture() {{ printf '%s' 'Try the new fullscreen renderer? (y/n)'; }}
|
||||
_sks_herdr() {{ return 0; }}
|
||||
rc=0
|
||||
SKS_DIALOG_TIMEOUT=1 send_keys_safe "my-project-worker-cline-01" "some text" || rc=$?
|
||||
SKS_DIALOG_TIMEOUT=1 send_keys_safe "my-project-worker-claude-01" "some text" || rc=$?
|
||||
echo "RC=$rc"
|
||||
"""
|
||||
res2 = _run_lib_helpers(script2)
|
||||
assert res2.returncode == 0, res2.stderr + res2.stdout
|
||||
assert "RC=2" in res2.stdout, f"Expected dialog blocking (RC=2) for cline modal: {res2.stdout}"
|
||||
assert "RC=2" in res2.stdout, f"Expected dialog blocking (RC=2) for claude modal: {res2.stdout}"
|
||||
|
||||
|
||||
|
||||
@@ -414,7 +431,7 @@ def test_c9_create_session_timeout_alive_pid_preserves_session(mam_sandbox, mock
|
||||
cmd = [
|
||||
"bash", str(script_path),
|
||||
"--workspace", str(mam_sandbox),
|
||||
"--agent", "cline",
|
||||
"--agent", "claude",
|
||||
"--role", "creator",
|
||||
"--session", "test-timeout-sess",
|
||||
"--onboard"
|
||||
@@ -454,7 +471,7 @@ def test_c10_create_session_timeout_dead_pid_kills_session(mam_sandbox, mock_her
|
||||
cmd = [
|
||||
"bash", str(script_path),
|
||||
"--workspace", str(mam_sandbox),
|
||||
"--agent", "cline",
|
||||
"--agent", "claude",
|
||||
"--role", "creator",
|
||||
"--session", "test-dead-sess"
|
||||
]
|
||||
@@ -471,7 +488,7 @@ def test_c10_create_session_timeout_dead_pid_kills_session(mam_sandbox, mock_her
|
||||
def test_adapter_strong_weak_partition():
|
||||
"""N-D: Verify strong_ready_tokens and weak_ready_tokens partition ready_tokens for all adapters."""
|
||||
from lib_py.agents.registry import get_adapter
|
||||
for agent in ('claude', 'agy', 'hermes', 'cline', 'grok'):
|
||||
for agent in ('claude', 'agy', 'hermes', 'grok'):
|
||||
adapter = get_adapter(agent)
|
||||
assert adapter is not None
|
||||
ready_set = set(t for t in adapter.ready_tokens.split('|') if t)
|
||||
@@ -502,26 +519,20 @@ check_sks() {
|
||||
send_keys_safe "$sess" "hello" 2>&1 || true
|
||||
}
|
||||
|
||||
echo "1: $(check_sks 'claude-reviewer-cline-01')"
|
||||
echo "2: $(check_sks 'my-claude-creator-cline')"
|
||||
echo "3: $(check_sks 'reviewer-creator-grok-01')"
|
||||
echo "4: $(check_sks 'planner-reviewer-claude-01')"
|
||||
echo "5: $(check_sks 'test-worker-hermes-02')"
|
||||
echo "6: $(check_sks 'workspace-creator-agy-05')"
|
||||
echo "7: $(check_sks 'claude')"
|
||||
echo "8: $(check_sks 'cline-01')"
|
||||
echo "9: $(check_sks 'grok-02')"
|
||||
"""
|
||||
res = _run_lib_helpers(script)
|
||||
assert res.returncode == 0, res.stderr + res.stdout
|
||||
assert "1: RESOLVED_AGENT=cline" in res.stdout
|
||||
assert "2: RESOLVED_AGENT=cline" in res.stdout
|
||||
assert "3: RESOLVED_AGENT=grok" in res.stdout
|
||||
assert "4: RESOLVED_AGENT=claude" in res.stdout
|
||||
assert "5: RESOLVED_AGENT=hermes" in res.stdout
|
||||
assert "6: RESOLVED_AGENT=agy" in res.stdout
|
||||
assert "7: RESOLVED_AGENT=claude" in res.stdout
|
||||
assert "8: RESOLVED_AGENT=cline" in res.stdout
|
||||
assert "9: RESOLVED_AGENT=grok" in res.stdout
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,17 @@ def run_lock_cmd(action, marker_path, env=None):
|
||||
def acquire_bg(marker_path, sleep_secs=10):
|
||||
cmd = f"source '{LOCK_SCRIPT}' && mam_acquire_loop_lock '{marker_path}' && sleep {sleep_secs}"
|
||||
proc = subprocess.Popen(["bash", "-c", cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
time.sleep(0.3)
|
||||
for _ in range(40):
|
||||
time.sleep(0.05)
|
||||
if marker_path.is_file():
|
||||
try:
|
||||
content = marker_path.read_text()
|
||||
if f"pid={proc.pid}" in content:
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if proc.poll() is not None:
|
||||
break
|
||||
return proc
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ ONBOARD_SH = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-orc-onboard" /
|
||||
RECONCILE_SH = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
|
||||
|
||||
SCRUBBED_ENV = dict(os.environ)
|
||||
for k in ["CLAUDE_CODE_SESSION_ID", "ANTIGRAVITY_CONVERSATION_ID", "HERMES_SESSION_ID", "CLINE_SESSION_ID", "MAM_ORCHESTRATOR_UUIDS"]:
|
||||
for k in ["CLAUDE_CODE_SESSION_ID", "ANTIGRAVITY_CONVERSATION_ID", "HERMES_SESSION_ID", "MAM_ORCHESTRATOR_UUIDS"]:
|
||||
SCRUBBED_ENV.pop(k, None)
|
||||
SCRUBBED_ENV["PYTHONPATH"] = f"{LIB_SH.parent}:{SCRUBBED_ENV.get('PYTHONPATH', '')}"
|
||||
|
||||
@@ -490,25 +490,10 @@ def test_o30_argv_precedes_env(mam_sandbox):
|
||||
assert env_uuid not in res_list.stdout
|
||||
|
||||
|
||||
# O-31: cline node launcher & non-UUID ID format (1785635248957_fajon)
|
||||
def test_o31_cline_node_launcher_id_format(mam_sandbox):
|
||||
cline_id = "1785635248957_fajon"
|
||||
env = {
|
||||
"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"),
|
||||
"HOME_DIR": str(mam_sandbox)
|
||||
}
|
||||
cmd = f"exec -a 'cline --id {cline_id}' bash '{ONBOARD_SH}'"
|
||||
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**SCRUBBED_ENV, **env}, cwd=str(mam_sandbox))
|
||||
assert res.returncode == 0
|
||||
|
||||
res_list = run_onboard(["--list"], env=env, cwd=str(mam_sandbox))
|
||||
assert cline_id in res_list.stdout
|
||||
|
||||
|
||||
# O-32: ID format strictness rejects invalid strings
|
||||
def test_o32_id_format_strictness(mam_sandbox):
|
||||
env = {"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "HOME_DIR": str(mam_sandbox)}
|
||||
for bad in ["../../etc/passwd", "a b", "fajon", "1785635248957"]:
|
||||
for bad in ["../../etc/passwd", "a b", "fajon", "1785635248957", "1785635248957_fajon"]:
|
||||
res = run_onboard(["--uuid", bad], env=env, cwd=str(mam_sandbox))
|
||||
assert res.returncode == 2
|
||||
|
||||
@@ -565,19 +550,6 @@ def test_o38_atomic_dump_yaml_initialization(mam_sandbox):
|
||||
assert orc_uuid in yaml_path.read_text()
|
||||
|
||||
|
||||
# O-39: cline agent family discovery exclusion in verify_session_uuid
|
||||
def test_o39_cline_orchestrator_exclusion(mam_sandbox):
|
||||
cline_id = "1785635248957_fajon"
|
||||
c_dir = mam_sandbox / ".cline" / "data" / "sessions" / cline_id
|
||||
c_dir.mkdir(parents=True, exist_ok=True)
|
||||
(c_dir / f"{cline_id}.json").write_text(json.dumps({"session_id": cline_id}))
|
||||
|
||||
state = {"orchestrator_uuids": [cline_id], "herdr_sessions": []}
|
||||
env = dump_state(mam_sandbox, state)
|
||||
row = {"name": "subagent", "cline_conversation_id_own": None, "pane": {"cwd": str(mam_sandbox)}}
|
||||
assert not run_verify_uuid(str(mam_sandbox), "cline", cline_id, row=row, mode="discover", env=env)
|
||||
|
||||
|
||||
# O-40: Malformed orchestrator_uuids logs warning to stderr and degrades gracefully
|
||||
def test_o40_malformed_orchestrator_uuids_degrades_open(mam_sandbox):
|
||||
sub_uuid = "02222222-2222-2222-2222-222222222222"
|
||||
|
||||
@@ -960,18 +960,18 @@ def test_grok_shell_and_scripts_integration(mam_sandbox):
|
||||
lib_path = mam_sandbox / "skills" / "lib.sh"
|
||||
lib_content = lib_path.read_text()
|
||||
assert '*-creator-grok|*-planner-grok|*-reviewer-grok) kind="grok"' in lib_content
|
||||
assert "'claude', 'agy', 'hermes', 'cline', 'grok'" in lib_content
|
||||
assert "'claude', 'agy', 'hermes', 'grok'" in lib_content
|
||||
assert '[[ "$sess" =~ "grok" ]]' in lib_content
|
||||
|
||||
# 2. create_session.sh validation
|
||||
create_path = mam_sandbox / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh"
|
||||
create_content = create_path.read_text()
|
||||
assert 'claude|agy|hermes|cline|grok)' in create_content
|
||||
assert 'claude|agy|hermes|grok)' in create_content
|
||||
|
||||
# 3. stop_session.sh validation & state capture
|
||||
stop_path = mam_sandbox / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh"
|
||||
stop_content = stop_path.read_text()
|
||||
assert 'claude|agy|hermes|cline|grok)' in stop_content
|
||||
assert 'claude|agy|hermes|grok)' in stop_content
|
||||
assert "target['grok_session_id_own'] = captured" in stop_content
|
||||
|
||||
# 4. workspace_uuid OWN_KEY
|
||||
|
||||
@@ -140,7 +140,7 @@ def test_comp_create_usage_matches_parser(mam_sandbox, mock_herdr, mock_agents):
|
||||
assert res.returncode == 0
|
||||
assert "--herdr-session" in res.stdout
|
||||
assert "--herdr-server" in res.stdout
|
||||
for agent in ("claude", "agy", "hermes", "cline"):
|
||||
for agent in ("claude", "agy", "hermes", "grok"):
|
||||
assert agent in res.stdout
|
||||
|
||||
# Test parser acceptance of valid flags vs unknown arg rejection
|
||||
@@ -1158,7 +1158,7 @@ def test_comp_stop_usage_matches_parser(mam_sandbox):
|
||||
assert dead not in res.stdout, f"usage() still advertises {dead}"
|
||||
|
||||
# 1b) 검증기가 받는 에이전트는 전부 도움말에 나온다 (Rev.2 M3)
|
||||
for agent in ("claude", "agy", "hermes", "cline"):
|
||||
for agent in ("claude", "agy", "hermes", "grok"):
|
||||
assert agent in res.stdout, f"usage() omits supported agent {agent}"
|
||||
|
||||
# 2) 도움말이 광고하는 플래그는 전부 파서가 받는다
|
||||
|
||||
Reference in New Issue
Block a user