feat(agents): Phase 8.4-10.5 built-in plugins, bundled skills, coordinator

This commit is contained in:
2026-04-04 23:24:34 +08:00
parent 88955ed550
commit d18167826e
105 changed files with 14780 additions and 15685 deletions

View File

@@ -16,10 +16,12 @@ from app.agents.graph import (
_create_child_agent,
_execute_tool_calls,
_parse_json_action,
_record_checkpoint,
_record_interrupt,
_record_recovery,
_route_agent_from_user_query,
_select_request_mode,
_set_phase,
_spawn_permission_for_role,
_run_collaboration_flow,
_run_sub_commander,
@@ -78,8 +80,23 @@ def _base_state(message: str, user_llm_config: dict | None = None) -> dict:
'verification_status': None,
'verification_summary': None,
'verification_evidence': [],
'isolation_mode': 'none',
'isolation_id': None,
'isolation_workspace_path': None,
'isolation_parent_conversation_id': None,
'isolation_metadata': {},
'input_tokens': 0,
'output_tokens': 0,
'estimated_cost': None,
'budget_warning': False,
'cost_by_agent': {},
'cost_thresholds': {},
'budget_state': None,
'collaboration_budget_history': [],
'current_phase': 'phase_0_bootstrap',
'phase_history': [{'phase': 'phase_0_bootstrap', 'reason': 'initial_state_created'}],
'current_checkpoint': 'bootstrap.initialized',
'checkpoint_history': [{'checkpoint': 'bootstrap.initialized', 'phase': 'phase_0_bootstrap', 'reason': 'initial_state_created'}],
'tool_strategy_used': None,
'tool_round_count': 0,
'max_tool_rounds': 2,
@@ -310,6 +327,24 @@ def test_initial_state_sets_structured_continuity_defaults():
assert state['clarification_context'] is None
assert state['event_trace'] == []
assert state['tool_outcomes'] == []
assert state['current_phase'] == 'phase_0_bootstrap'
assert state['current_checkpoint'] == 'bootstrap.initialized'
assert state['phase_history'][-1]['phase'] == 'phase_0_bootstrap'
assert state['checkpoint_history'][-1]['checkpoint'] == 'bootstrap.initialized'
def test_set_phase_and_record_checkpoint_append_history():
state = _base_state('test')
_set_phase(state, 'phase_1_routing', reason='entered_master')
_record_checkpoint(state, 'routing.master_entered', reason='entered_master')
assert state['current_phase'] == 'phase_1_routing'
assert state['current_checkpoint'] == 'routing.master_entered'
assert state['phase_history'][-1]['phase'] == 'phase_1_routing'
assert state['checkpoint_history'][-1]['checkpoint'] == 'routing.master_entered'
assert 'agent.phase.changed' in [event['event_type'] for event in state['event_trace']]
assert 'agent.checkpoint.recorded' in [event['event_type'] for event in state['event_trace']]
def test_spawn_permission_for_role_uses_registry_policy():
@@ -627,6 +662,15 @@ async def test_run_collaboration_flow_collects_task_results_and_verifies(monkeyp
assert result['message_trace'][-1]['message_type'] == 'task_update'
assert 'agent.created' in [event['event_type'] for event in result['event_trace']]
assert 'agent.message.sent' in [event['event_type'] for event in result['event_trace']]
assert 'agent.phase.changed' in [event['event_type'] for event in result['event_trace']]
assert 'agent.checkpoint.recorded' in [event['event_type'] for event in result['event_trace']]
assert result['current_phase'] == 'phase_4_visibility_and_verification'
assert result['current_checkpoint'] == 'collaboration.completed'
assert [entry['phase'] for entry in result['phase_history']][-3:] == [
'phase_2_controlled_collaboration',
'phase_3_dynamic_collaboration',
'phase_4_visibility_and_verification',
]
assert 'agent.spawn.blocked' not in [event['event_type'] for event in result['event_trace']]
assert result['spawned_agent_ids']
assert all(not agent_id.startswith('blocked-') for agent_id in result['spawned_agent_ids'])
@@ -637,6 +681,8 @@ async def test_master_node_enters_collaboration_mode_for_complex_multi_role_requ
async def fake_collaboration_flow(state, user_query):
state['execution_mode'] = 'collaboration'
state['final_response'] = 'collaboration done'
state['current_phase'] = 'phase_4_visibility_and_verification'
state['current_checkpoint'] = 'collaboration.completed'
state['messages'] = [*state.get('messages', []), AIMessage(content=state['final_response'])]
return state
@@ -647,6 +693,31 @@ async def test_master_node_enters_collaboration_mode_for_complex_multi_role_requ
assert result['execution_mode'] == 'collaboration'
assert result['final_response'] == 'collaboration done'
assert result['current_phase'] == 'phase_4_visibility_and_verification'
assert result['current_checkpoint'] == 'collaboration.completed'
async def test_run_collaboration_flow_fallback_restores_routing_phase(monkeypatch):
monkeypatch.setattr(graph_module, '_build_collaboration_tasks', lambda _user_query: [
AgentTask(
task_id='task-1',
title='单任务',
role=AgentRole.LIBRARIAN.value,
owner_agent_id=AgentRole.LIBRARIAN.value,
goal='检索资料',
expected_evidence=[{'type': 'evidence'}],
)
])
state = _base_state('帮我搜一下资料')
result = await _run_collaboration_flow(state, '帮我搜一下资料')
assert result['execution_mode'] == 'direct'
assert result['routing_decision']['reason'] == 'collaboration_plan_fell_back'
assert result['current_phase'] == 'phase_1_routing'
assert result['current_checkpoint'] == 'routing.direct_resumed'
assert result['checkpoint_history'][-2]['checkpoint'] == 'collaboration.fallback_to_direct'
assert result['checkpoint_history'][-1]['checkpoint'] == 'routing.direct_resumed'
async def test_master_node_returns_stable_reply_for_simple_greeting(monkeypatch):
@@ -1404,6 +1475,78 @@ def test_build_verifier_hints_uses_capability_metadata():
assert '提醒创建成功' in hints['result_preview']
def test_prepare_isolation_context_selects_session_for_stateful_tools():
state = _base_state('reminder request')
graph_module._prepare_isolation_context(
state,
role=AgentRole.SCHEDULE_PLANNER,
sub_commander='schedule_planning',
user_query='create a reminder for tomorrow morning and keep the intermediate state isolated',
toolset=[FakeTool('create_reminder', 'ok')],
)
assert state['isolation_mode'] == 'session'
assert state['isolation_workspace_path'] is None
assert state['isolation_metadata']['reason'] == 'stateful_or_non_parallel_tooling'
assert state['event_trace'][-1]['event_type'] == 'agent.isolation.selected'
def test_prepare_isolation_context_uses_worktree_for_repo_mutation_queries(monkeypatch):
state = _base_state('fix repo build and create patch')
monkeypatch.setattr(
graph_module,
'prepare_worktree_isolation',
lambda **kwargs: {
'mode': 'worktree',
'isolation_id': 'worktree-test',
'workspace_path': '/tmp/jarvis/worktree-test',
'parent_conversation_id': 'c1',
'metadata': {
'reason': kwargs['decision'].reason,
'branch': 'jarvis/c1/executor-worktree-test',
},
},
)
graph_module._prepare_isolation_context(
state,
role=AgentRole.EXECUTOR,
sub_commander='executor_tasks',
user_query='fix repo build and create patch for the failing tests',
toolset=[FakeTool('create_task', 'ok')],
)
assert state['isolation_mode'] == 'worktree'
assert state['isolation_workspace_path'] == '/tmp/jarvis/worktree-test'
assert state['isolation_metadata']['branch'] == 'jarvis/c1/executor-worktree-test'
assert state['event_trace'][-1]['event_type'] == 'agent.isolation.selected'
def test_record_response_usage_updates_state_cost_totals_and_budget_warning():
state = _base_state('test')
state['cost_thresholds'] = {'total_tokens': 100, 'estimated_cost': 0.0001}
graph_module._record_response_usage(
state,
AIMessage(
content='ok',
usage_metadata={'input_tokens': 60, 'output_tokens': 50, 'total_tokens': 110},
),
)
assert state['input_tokens'] == 60
assert state['output_tokens'] == 50
assert state['estimated_cost'] == 0.00093
assert state['budget_warning'] is True
assert state['cost_by_agent'][AgentRole.MASTER.value]['total_tokens'] == 110
assert [event['event_type'] for event in state['event_trace']] == [
'agent.cost.updated',
'agent.cost.warning',
]
async def test_execute_tool_calls_records_schema_events_and_aggregate_summaries(monkeypatch):
tool = FakeTool('create_reminder', '提醒创建成功: 开会 @ 2026-03-29 09:00')
state = _base_state('test')

View File

@@ -135,6 +135,45 @@ async def visibility_env(tmp_path):
'verification_evidence': [
{'task_id': 'task-1', 'status': 'passed', 'summary': 'Verified'}
],
'execution_mode': 'collaboration',
'current_phase': 'phase_4_visibility_and_verification',
'current_checkpoint': 'visibility.runtime_summary_ready',
'phase_history': [
{'phase': 'phase_0_bootstrap'},
{'phase': 'phase_4_visibility_and_verification'},
],
'checkpoint_history': [
{'checkpoint': 'bootstrap.initialized'},
{'checkpoint': 'visibility.runtime_summary_ready'},
],
'input_tokens': 120,
'output_tokens': 80,
'budget_warning': True,
'estimated_cost': 0.00156,
'cost_thresholds': {'total_tokens': 150, 'estimated_cost': 0.001},
'cost_by_agent': {
'master': {
'agent_id': 'master',
'input_tokens': 60,
'output_tokens': 20,
'total_tokens': 80,
'estimated_cost': 0.00048,
'budget_warning': False,
},
'analyst-1234abcd': {
'agent_id': 'analyst-1234abcd',
'input_tokens': 60,
'output_tokens': 60,
'total_tokens': 120,
'estimated_cost': 0.00108,
'budget_warning': True,
},
},
'isolation_mode': 'worktree',
'isolation_id': 'iso-1',
'isolation_workspace_path': '/tmp/jarvis/worktree-1',
'isolation_parent_conversation_id': 'parent-conv-1',
'isolation_metadata': {'branch': 'jarvis/test-worker'},
},
}
@@ -396,6 +435,87 @@ async def test_visibility_verifier_returns_verdict(visibility_env):
assert payload['evidence'][0]['task_id'] == ids['task_id']
@pytest.mark.asyncio
async def test_visibility_runtime_summary_returns_phase_cost_and_isolation_metadata(visibility_env):
app, ids = visibility_env
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url='http://testserver') as client:
response = await client.get(
'/api/agents/visibility/runtime-summary',
params={'conversation_id': ids['conversation_id']},
)
assert response.status_code == 200
payload = response.json()
assert payload['conversation_id'] == ids['conversation_id']
assert payload['execution_mode'] == 'collaboration'
assert payload['current_phase'] == 'phase_4_visibility_and_verification'
assert payload['current_checkpoint'] == 'visibility.runtime_summary_ready'
assert payload['verifier']['status'] == 'passed'
assert payload['isolation']['mode'] == 'worktree'
assert payload['isolation']['workspace_path'] == '/tmp/jarvis/worktree-1'
assert payload['isolation']['metadata']['branch'] == 'jarvis/test-worker'
assert payload['cost']['input_tokens'] == 120
assert payload['cost']['output_tokens'] == 80
assert payload['cost']['total_tokens'] == 200
assert payload['cost']['estimated_cost'] == 0.00156
assert payload['cost']['budget_warning'] is True
assert payload['topology_node_count'] == 2
assert payload['active_task_count'] == 1
assert payload['completed_task_count'] == 1
assert payload['recent_events'][0]['event_id'] == 'evt-1'
@pytest.mark.asyncio
async def test_visibility_cost_returns_totals_thresholds_and_agent_breakdown(visibility_env):
app, ids = visibility_env
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url='http://testserver') as client:
response = await client.get(
'/api/agents/visibility/cost',
params={'conversation_id': ids['conversation_id']},
)
assert response.status_code == 200
payload = response.json()
assert payload['total']['input_tokens'] == 120
assert payload['total']['output_tokens'] == 80
assert payload['total']['total_tokens'] == 200
assert payload['total']['budget_warning'] is True
assert payload['thresholds']['total_tokens'] == 150
assert payload['thresholds']['estimated_cost'] == 0.001
assert payload['by_agent'][0]['agent_id'] == 'analyst-1234abcd'
assert payload['by_agent'][0]['budget_warning'] is True
assert payload['by_agent'][1]['agent_id'] == 'master'
@pytest.mark.asyncio
async def test_visibility_tools_returns_governance_metadata_and_usage_counts(visibility_env):
app, ids = visibility_env
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url='http://testserver') as client:
response = await client.get(
'/api/agents/visibility/tools',
params={'conversation_id': ids['conversation_id']},
)
assert response.status_code == 200
payload = response.json()
assert payload['total_tools'] >= 1
assert payload['used_tools'] >= 1
search_tool = next(item for item in payload['items'] if item['tool_name'] == 'search_web')
assert search_tool['permission_class'] == 'external'
assert search_tool['side_effect_scope'] == 'network'
assert search_tool['usage_count'] == 1
assert search_tool['last_result_preview'] == 'ok'
assert payload['upgrade_candidates'] == [
'worktree_manager',
'cost_inspector',
'runtime_event_drilldown',
'tool_policy_explorer',
]
@pytest.mark.asyncio
async def test_visibility_events_reject_invalid_datetime(visibility_env):
app, ids = visibility_env

View File

@@ -13,7 +13,7 @@ from app.models.conversation import Conversation, Message
from app.models.memory import MemorySummary, UserMemory
from app.models.user import User
from app.services import agent_service, memory_service
from app.services.agent_service import AgentService
from app.services.agent_service import AgentService, _build_continuity_snapshot, _extract_continuity_snapshot
from app.services.auth_service import get_password_hash
from app.services.document_service import DocumentService
@@ -23,6 +23,32 @@ class FakeGraph:
return {"final_response": "已记录你的请求。"}
def test_continuity_snapshot_roundtrip_preserves_phase_and_checkpoint():
payload = {
"current_agent": "master",
"current_phase": "phase_4_visibility_and_verification",
"phase_history": [
{"phase": "phase_0_bootstrap", "reason": "initial_state_created"},
{"phase": "phase_4_visibility_and_verification", "reason": "verification_started"},
],
"current_checkpoint": "collaboration.completed",
"checkpoint_history": [
{"checkpoint": "bootstrap.initialized", "phase": "phase_0_bootstrap", "reason": "initial_state_created"},
{"checkpoint": "collaboration.completed", "phase": "phase_4_visibility_and_verification", "reason": "collaboration_flow_finished"},
],
}
snapshot = _build_continuity_snapshot(payload)
assert snapshot is not None
restored = _extract_continuity_snapshot({"kind": "agent_continuity_state", **snapshot})
assert restored is not None
assert restored["current_phase"] == "phase_4_visibility_and_verification"
assert restored["current_checkpoint"] == "collaboration.completed"
assert restored["phase_history"][-1]["phase"] == "phase_4_visibility_and_verification"
assert restored["checkpoint_history"][-1]["checkpoint"] == "collaboration.completed"
class FakeStreamingGraph:
async def astream_events(self, state, version="v2"):
yield {