Files
synapbus/tests/e2e/scenarios/test_task_auction.py
T
Algis DumbrisandClaude Opus 4.6 b88d52276a feat: add admin CLI, API keys, threads, and Slack-like UI redesign
Major feature additions across backend and frontend:

Backend:
- Unix domain socket admin server with JSON-RPC protocol
- CLI subcommands: user/agent management, audit, backup, messages, channels
- Managed API keys (sb_ prefix) with permissions, channel limits, expiry
- Thread/reply support in messaging core (reply_to column)
- Context-based trace owner_id propagation for proper audit filtering
- Two-step auth middleware supporting both agent keys and managed API keys

Frontend:
- Complete Slack-like dark theme redesign with custom CSS properties
- Sidebar with Channels, Direct Messages, and Admin sections
- Thread panel (slide-in) for viewing message replies
- API key management page with create form, key display, and
  ready-to-use MCP/Claude Code config snippets with copy-to-clipboard
- All pages restyled: login, dashboard, agents, conversations, settings

E2E Tests:
- Fixed test runner binary path resolution
- Added pyproject.toml for test dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:10:51 +02:00

195 lines
7.9 KiB
Python

"""Scenario: Task auction lifecycle.
3 agents (auction_poster, auction_bidder1, auction_bidder2):
- Poster creates auction channel
- Bidders join
- Poster posts a task
- Both bidders submit bids
- Poster accepts bidder1's bid
- Bidder1 completes the task
"""
from __future__ import annotations
import time
import traceback
from typing import Any, Dict, List
import anthropic
from ..lib.agent_runner import AgentRun, TestResult, ToolCall, make_verification, run_agent
from ..lib.mcp_client import SynapBusMCP
from ..lib.setup import register_agents
from ..lib.tools import get_tools_for_scenario
SCENARIO_NAME = "task_auction"
DESCRIPTION = "Task auction lifecycle: post task, bid, accept bid, complete task"
def run(claude: anthropic.Anthropic, base_url: str, model: str) -> TestResult:
start = time.time()
agents_list: List[str] = ["auction_poster", "auction_bidder1", "auction_bidder2"]
runs: List[AgentRun] = []
verifications: List[Dict[str, Any]] = []
try:
creds = register_agents(base_url, [
{"name": "auction_poster", "display_name": "Task Poster",
"capabilities": {"project_management": True}},
{"name": "auction_bidder1", "display_name": "Bidder One",
"capabilities": {"python": True, "ml": True}},
{"name": "auction_bidder2", "display_name": "Bidder Two",
"capabilities": {"golang": True, "devops": True}},
])
poster_cred, bidder1_cred, bidder2_cred = creds
poster_mcp = SynapBusMCP(base_url, poster_cred.api_key)
bidder1_mcp = SynapBusMCP(base_url, bidder1_cred.api_key)
bidder2_mcp = SynapBusMCP(base_url, bidder2_cred.api_key)
poster_mcp.initialize()
bidder1_mcp.initialize()
bidder2_mcp.initialize()
tools = get_tools_for_scenario(SCENARIO_NAME)
# Step 1: Create auction channel (direct MCP)
print("\n [auction] Creating auction channel...")
ch_result = poster_mcp.call_tool("create_channel", {
"name": "task-market",
"description": "Task marketplace",
"type": "auction",
})
ch_created = "channel_id" in ch_result or "name" in ch_result
verifications.append(make_verification("Auction channel created", ch_created,
str(ch_result)[:200]))
# Step 2: Bidders join
print(" [auction] Bidders joining channel...")
b1_join = bidder1_mcp.call_tool("join_channel", {"channel_name": "task-market"})
b2_join = bidder2_mcp.call_tool("join_channel", {"channel_name": "task-market"})
verifications.append(make_verification("Bidder1 joined", b1_join.get("status") == "joined"))
verifications.append(make_verification("Bidder2 joined", b2_join.get("status") == "joined"))
# Step 3: Poster posts a task (direct MCP)
print(" [auction] Posting task...")
task_result = poster_mcp.call_tool("post_task", {
"channel_name": "task-market",
"title": "Build ML Pipeline",
"description": "Build a data processing pipeline with feature engineering and model training",
"requirements": '{"skills": ["python", "ml"], "experience": "intermediate"}',
})
task_id = task_result.get("task_id")
verifications.append(make_verification(
"Task posted", task_id is not None,
"task_id={}".format(task_id)))
if task_id is None:
raise ValueError("Task creation failed: {}".format(task_result))
# Step 4: Bidder1 bids (via Claude for variety)
print("\n [auction] Bidder1 submitting bid...")
bidder1_run = run_agent(
claude, bidder1_mcp, "auction_bidder1",
system_prompt=(
"You are Bidder One, a Python/ML specialist on SynapBus. "
"Submit a bid on a task. Be concise."
),
user_prompt=(
"Submit a bid on task_id {task_id} using the bid_task tool. "
"Include your capabilities as JSON: '{{\"python\": true, \"ml\": true}}', "
"time_estimate '3 days', and message explaining why you are a good fit."
).format(task_id=task_id),
tools=tools, model=model, max_tool_rounds=2,
)
runs.append(bidder1_run)
bid1_submitted = any(tc.tool == "bid_task" and tc.success for tc in bidder1_run.tool_calls)
# Extract bid_id from tool output
bid1_id = None
for tc in bidder1_run.tool_calls:
if tc.tool == "bid_task" and tc.success:
bid1_id = tc.output.get("bid_id")
verifications.append(make_verification(
"Bidder1 submitted bid", bid1_submitted,
"bid_id={}".format(bid1_id)))
# Step 5: Bidder2 bids (direct MCP)
print(" [auction] Bidder2 submitting bid...")
bid2_result = bidder2_mcp.call_tool("bid_task", {
"task_id": task_id,
"capabilities": '{"golang": true, "devops": true}',
"time_estimate": "5 days",
"message": "I can handle the infrastructure side with Go",
})
bid2_id = bid2_result.get("bid_id")
verifications.append(make_verification(
"Bidder2 submitted bid", bid2_id is not None,
"bid_id={}".format(bid2_id)))
# Step 6: Poster accepts Bidder1's bid
if bid1_id is not None:
print(" [auction] Poster accepting Bidder1's bid...")
accept_result = poster_mcp.call_tool("accept_bid", {
"task_id": task_id,
"bid_id": bid1_id,
})
accepted = accept_result.get("status") == "accepted"
verifications.append(make_verification(
"Poster accepted Bidder1's bid", accepted,
str(accept_result)[:200]))
else:
verifications.append(make_verification(
"Poster accepted Bidder1's bid", False, "No bid_id available"))
# Step 7: Bidder1 completes task
print(" [auction] Bidder1 completing task...")
complete_result = bidder1_mcp.call_tool("complete_task", {"task_id": task_id})
completed = complete_result.get("status") == "completed"
verifications.append(make_verification(
"Bidder1 completed task", completed,
str(complete_result)[:200]))
# Step 8: Verify final task state
print(" [auction] Verifying task state...")
tasks = poster_mcp.call_tool("list_tasks", {
"channel_name": "task-market",
"status": "completed",
})
completed_tasks = tasks.get("tasks", [])
task_completed = any(t.get("id") == task_id for t in completed_tasks)
verifications.append(make_verification(
"Task shows as completed", task_completed,
"{} completed tasks found".format(len(completed_tasks))))
poster_mcp.close()
bidder1_mcp.close()
bidder2_mcp.close()
all_passed = all(v["passed"] for v in verifications)
return TestResult(
name=SCENARIO_NAME,
description=DESCRIPTION,
status="pass" if all_passed else "fail",
agents=agents_list,
runs=runs,
verifications=verifications,
error=None,
duration=time.time() - start,
total_input_tokens=sum(r.input_tokens for r in runs),
total_output_tokens=sum(r.output_tokens for r in runs),
)
except Exception as e:
return TestResult(
name=SCENARIO_NAME,
description=DESCRIPTION,
status="error",
agents=agents_list,
runs=runs,
verifications=verifications,
error=traceback.format_exc(),
duration=time.time() - start,
total_input_tokens=sum(r.input_tokens for r in runs),
total_output_tokens=sum(r.output_tokens for r in runs),
)