missing commits
This commit is contained in:
@@ -9,5 +9,5 @@ active = "main"
|
|||||||
|
|
||||||
[discussions.main]
|
[discussions.main]
|
||||||
git_commit = ""
|
git_commit = ""
|
||||||
last_updated = "2026-05-02T12:45:18"
|
last_updated = "2026-05-02T14:52:30"
|
||||||
history = []
|
history = []
|
||||||
|
|||||||
@@ -182,6 +182,9 @@ def _get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optio
|
|||||||
base += f"\n\n{strategy}"
|
base += f"\n\n{strategy}"
|
||||||
return base
|
return base
|
||||||
|
|
||||||
|
def get_combined_system_prompt(preset: Optional[ToolPreset] = None, bias: Optional[BiasProfile] = None) -> str:
|
||||||
|
return _get_combined_system_prompt(preset, bias)
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
_comms_log: deque[dict[str, Any]] = deque(maxlen=1000)
|
_comms_log: deque[dict[str, Any]] = deque(maxlen=1000)
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
# Read prompt from stdin
|
||||||
|
try:
|
||||||
|
prompt = sys.stdin.read()
|
||||||
|
except Exception:
|
||||||
|
prompt = ""
|
||||||
|
|
||||||
|
# 1. Epic Initialization
|
||||||
|
if 'PATH: Epic Initialization' in prompt:
|
||||||
|
mock_response = [
|
||||||
|
{"id": "track-a", "goal": "Track A Goal", "title": "Track A"},
|
||||||
|
{"id": "track-b", "goal": "Track B Goal", "title": "Track B"}
|
||||||
|
]
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": json.dumps(mock_response)
|
||||||
|
}), flush=True)
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "result",
|
||||||
|
"status": "success",
|
||||||
|
"stats": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50},
|
||||||
|
"session_id": "mock-epic"
|
||||||
|
}), flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. Sprint Planning (different tickets for different tracks)
|
||||||
|
if 'generate the implementation tickets' in prompt:
|
||||||
|
track_label = "A" if "Track A" in prompt else "B"
|
||||||
|
mock_response = [
|
||||||
|
{"id": f"ticket-{track_label}-1", "description": f"Ticket {track_label} 1", "status": "todo", "assigned_to": "worker", "depends_on": []}
|
||||||
|
]
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": json.dumps(mock_response)
|
||||||
|
}), flush=True)
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "result",
|
||||||
|
"status": "success",
|
||||||
|
"stats": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50},
|
||||||
|
"session_id": f"mock-sprint-{track_label}"
|
||||||
|
}), flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 3. Worker Execution
|
||||||
|
if 'You are assigned to Ticket' in prompt:
|
||||||
|
# Extract ticket ID
|
||||||
|
import re
|
||||||
|
match = re.search(r'Ticket (ticket-[A-B]-1)', prompt)
|
||||||
|
tid = match.group(1) if match else "unknown"
|
||||||
|
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": f"Working on {tid}. Done."
|
||||||
|
}), flush=True)
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "result",
|
||||||
|
"status": "success",
|
||||||
|
"stats": {"total_tokens": 50, "input_tokens": 25, "output_tokens": 25},
|
||||||
|
"session_id": f"mock-worker-{tid}"
|
||||||
|
}), flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Default
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Mock response"
|
||||||
|
}), flush=True)
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "result",
|
||||||
|
"status": "success",
|
||||||
|
"stats": {"total_tokens": 10, "input_tokens": 5, "output_tokens": 5},
|
||||||
|
"session_id": "mock-default"
|
||||||
|
}), flush=True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import pytest
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Ensure project root is in path
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||||
|
|
||||||
|
from src import api_hook_client
|
||||||
|
|
||||||
|
def _poll_mma_status(client, timeout, condition, label):
|
||||||
|
"""Poll get_mma_status() until condition(status) is True or timeout."""
|
||||||
|
last_status = {}
|
||||||
|
for i in range(timeout):
|
||||||
|
status = client.get_mma_status() or {}
|
||||||
|
if condition(status):
|
||||||
|
return True, status
|
||||||
|
last_status = status
|
||||||
|
time.sleep(1)
|
||||||
|
return False, last_status
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
@pytest.mark.timeout(300)
|
||||||
|
def test_mma_concurrent_tracks_execution(live_gui) -> None:
|
||||||
|
"""
|
||||||
|
Stress test for concurrent MMA track execution.
|
||||||
|
Verifies that starting multiple tracks simultaneously doesn't cause crashes
|
||||||
|
and that workers from both tracks are processed.
|
||||||
|
"""
|
||||||
|
client = api_hook_client.ApiHookClient()
|
||||||
|
assert client.wait_for_server(timeout=15), "Hook server did not start"
|
||||||
|
|
||||||
|
# 1. Setup provider to custom mock
|
||||||
|
mock_path = os.path.abspath("tests/mock_concurrent_mma.py")
|
||||||
|
client.set_value('current_provider', 'gemini_cli')
|
||||||
|
client.set_value('gcli_path', f'"{sys.executable}" "{mock_path}"')
|
||||||
|
client.click('btn_project_save')
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
# 2. Plan Epic to generate tracks
|
||||||
|
print("[SIM] Planning Epic...")
|
||||||
|
client.set_value('mma_epic_input', 'PATH: Epic Initialization')
|
||||||
|
client.click('btn_mma_plan_epic')
|
||||||
|
|
||||||
|
ok, status = _poll_mma_status(client, 60,
|
||||||
|
lambda s: len(s.get('proposed_tracks', [])) >= 2, "plan-epic")
|
||||||
|
assert ok, f"Proposed tracks not found: {status.get('proposed_tracks')}"
|
||||||
|
print(f"[SIM] Found {len(status['proposed_tracks'])} proposed tracks.")
|
||||||
|
|
||||||
|
# 3. Accept tracks and wait for them to be created
|
||||||
|
print("[SIM] Accepting tracks...")
|
||||||
|
client.click('btn_mma_accept_tracks')
|
||||||
|
ok, status = _poll_mma_status(client, 30,
|
||||||
|
lambda s: len(s.get('tracks', [])) >= 2, "accept-tracks")
|
||||||
|
assert ok, "Tracks not created in project"
|
||||||
|
|
||||||
|
tracks = status.get('tracks', [])
|
||||||
|
track_a_id = next(t['id'] for t in tracks if 'track-a' in t['id'] or 'Track A' in t['title'])
|
||||||
|
track_b_id = next(t['id'] for t in tracks if 'track-b' in t['id'] or 'Track B' in t['title'])
|
||||||
|
print(f"[SIM] Track IDs: A={track_a_id}, B={track_b_id}")
|
||||||
|
|
||||||
|
# 4. Start BOTH tracks concurrently
|
||||||
|
print(f"[SIM] Starting Track A ({track_a_id})...")
|
||||||
|
client.click('btn_mma_start_track', user_data=track_a_id)
|
||||||
|
|
||||||
|
# Tiny sleep to allow the first one to initialize its engine
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
print(f"[SIM] Starting Track B ({track_b_id})...")
|
||||||
|
client.click('btn_mma_start_track', user_data=track_b_id)
|
||||||
|
|
||||||
|
# 5. Verify workers from BOTH tracks appear in mma_streams
|
||||||
|
print("[SIM] Verifying concurrent worker activity...")
|
||||||
|
seen_a = False
|
||||||
|
seen_b = False
|
||||||
|
|
||||||
|
for i in range(40):
|
||||||
|
res = client.get_mma_workers() or {}
|
||||||
|
workers = res.get('workers', {})
|
||||||
|
stream_ids = workers.keys()
|
||||||
|
|
||||||
|
if any("ticket-A-1" in sid for sid in stream_ids):
|
||||||
|
seen_a = True
|
||||||
|
if any("ticket-B-1" in sid for sid in stream_ids):
|
||||||
|
seen_b = True
|
||||||
|
|
||||||
|
if seen_a and seen_b:
|
||||||
|
print(f"[SIM] SUCCESS: Observed workers from both tracks at t={i}s")
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
if i % 5 == 0:
|
||||||
|
print(f"[SIM] t={i}s: seen_a={seen_a}, seen_b={seen_b}, active_streams={list(stream_ids)}")
|
||||||
|
|
||||||
|
assert seen_a, "Worker from Track A never appeared in mma_streams"
|
||||||
|
assert seen_b, "Worker from Track B never appeared in mma_streams"
|
||||||
|
|
||||||
|
# 6. Wait for both tracks to complete
|
||||||
|
print("[SIM] Waiting for tracks to complete...")
|
||||||
|
# Each track has 1 ticket in our mock.
|
||||||
|
completed_a = False
|
||||||
|
completed_b = False
|
||||||
|
|
||||||
|
for i in range(60):
|
||||||
|
res = client.get_mma_workers() or {}
|
||||||
|
workers = res.get('workers', {})
|
||||||
|
|
||||||
|
# Check stream content for completion marker from our mock
|
||||||
|
for sid, text in workers.items():
|
||||||
|
if "ticket-A-1" in sid:
|
||||||
|
if "Done." in text:
|
||||||
|
completed_a = True
|
||||||
|
else:
|
||||||
|
if i % 10 == 0: print(f"[SIM] t={i}s: Stream A: {text[:50]}...")
|
||||||
|
if "ticket-B-1" in sid:
|
||||||
|
if "Done." in text:
|
||||||
|
completed_b = True
|
||||||
|
else:
|
||||||
|
if i % 10 == 0: print(f"[SIM] t={i}s: Stream B: {text[:50]}...")
|
||||||
|
|
||||||
|
if completed_a and completed_b:
|
||||||
|
print(f"[SIM] Both tracks completed at t={i}s")
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
assert completed_a, "Track A did not complete"
|
||||||
|
assert completed_b, "Track B did not complete"
|
||||||
|
|
||||||
|
# 7. Final stability check - ensure no crashes occurred in Hook Server
|
||||||
|
status = client.get_mma_status()
|
||||||
|
assert status is not None
|
||||||
|
assert status.get('mma_status') in ['done', 'idle', 'running']
|
||||||
|
|
||||||
|
print("[SIM] Concurrent MMA tracks stress test PASSED.")
|
||||||
@@ -56,12 +56,10 @@ def test_mma_step_mode_approval_flow(live_gui) -> None:
|
|||||||
tickets = status.get('active_tickets', [])
|
tickets = status.get('active_tickets', [])
|
||||||
tid = tickets[0]['id']
|
tid = tickets[0]['id']
|
||||||
|
|
||||||
# 4. Attempt to approve (THIS SHOULD FAIL OR DO NOTHING CURRENTLY)
|
# 4. Attempt to approve
|
||||||
print(f"[SIM] Attempting to approve ticket {tid}...")
|
print(f"[SIM] Attempting to approve ticket {tid}...")
|
||||||
# We'll try to use mutate_mma_dag to set status to in_progress
|
res = client.approve_mma_ticket(tid)
|
||||||
# (Note: this uses the bugged '_mutate_dag' endpoint internally if not fixed)
|
print(f"[SIM] Approve result: {res}")
|
||||||
res = client.mutate_mma_dag({"ticket_id": tid, "status": "in_progress"})
|
|
||||||
print(f"[SIM] Mutate result: {res}")
|
|
||||||
|
|
||||||
# 5. Verify it moved to in_progress
|
# 5. Verify it moved to in_progress
|
||||||
ok, status = _poll_mma_status(client, timeout=10, label="verify-in-progress",
|
ok, status = _poll_mma_status(client, timeout=10, label="verify-in-progress",
|
||||||
|
|||||||
Reference in New Issue
Block a user