from __future__ import annotations import contextlib import shutil import unittest import uuid from pathlib import Path from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock, patch import yaml from pydantic import ValidationError from opc.core.config import AgentsConfig, ExternalAgentConfig, OPCConfig from opc.core.models import ( DelegationWorkItem, ExecutionMode, RouterDecision, SessionMessageRecord, SessionPartRecord, Task, TaskStatus, UserMessage, WorkItemExecutionStrategy, ) from opc.engine import OPCEngine from opc.layer2_organization.company_mode import CompanyWorkItemExecutor from opc.layer3_agent.adapters.claude_code import ClaudeCodeAdapter from opc.layer3_agent.adapters.codex_adapter import CodexAdapter from opc.layer3_agent.adapters.cursor_adapter import CursorAdapter from opc.layer3_agent.adapters.opencode_adapter import OpenCodeAdapter from opc.layer3_agent.adapters.registry import AdapterRegistry from opc.layer2_organization.org_engine import TASK_MODE_GENERAL_ROLE_ID @contextlib.contextmanager def _workspace_tempdir() -> Path: base = Path.cwd() / ".tmp-test" / f"runtime-config-{uuid.uuid4().hex}" base.mkdir(parents=True, exist_ok=True) try: yield base finally: shutil.rmtree(base, ignore_errors=True) class RuntimeConfigEnforcementTests(unittest.IsolatedAsyncioTestCase): def test_role_prompt_task_uses_skip_history_flag_not_boolean_runtime_resume(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") source_task = Task( id="delivery-task", title="Delivery", description="Final delivery.", assigned_to="chao", project_id="proj1", session_id="sess1", metadata={"work_item_role_id": "chao"}, ) prompt_task = engine._build_role_prompt_task( source_task, prompt_kind="ceo_pre_delivery_assessment", description="Assessment payload", execution_agent="native", system_prompt="Return JSON only.", ) self.assertNotIn("runtime_resume", prompt_task.context_snapshot) self.assertTrue(prompt_task.context_snapshot["skip_session_history"]) def test_external_agent_approval_mode_allows_only_three_modes(self) -> None: for mode in ("user-settings", "auto", "full-auto"): config = ExternalAgentConfig(command="codex", approval_mode=mode) self.assertEqual(config.approval_mode, mode) for invalid_mode in ("manual", "force", "ask"): with self.assertRaises(ValidationError): ExternalAgentConfig(command="codex", approval_mode=invalid_mode) def test_external_agent_defaults_use_install_ready_modes(self) -> None: config = AgentsConfig() self.assertEqual(config.agents["codex"].approval_mode, "auto") self.assertEqual(config.agents["claude_code"].approval_mode, "full-auto") self.assertEqual(config.agents["cursor"].approval_mode, "full-auto") self.assertEqual(config.agents["opencode"].approval_mode, "full-auto") self.assertEqual(config.agents["opencode"].model, "") def test_load_migrates_legacy_external_agent_approval_modes_once(self) -> None: with _workspace_tempdir() as config_dir: agent_config_path = config_dir / "agent_config.yaml" agent_config_path.write_text( yaml.dump( { "external_agents": { "preferred_order": ["claude_code", "codex", "cursor", "opencode"], "claude_code": {"command": "claude", "approval_mode": "bypass"}, "codex": {"command": "codex", "approval_mode": "delegate"}, "cursor": {"command": "cursor", "approval_mode": "delegate"}, "opencode": {"command": "opencode", "approval_mode": "delegate"}, } } ), encoding="utf-8", ) config = OPCConfig.load(config_dir) self.assertEqual( {name: agent.approval_mode for name, agent in config.agents.agents.items()}, { "claude_code": "auto", "codex": "auto", "cursor": "auto", "opencode": "auto", }, ) reloaded = yaml.safe_load(agent_config_path.read_text(encoding="utf-8")) self.assertEqual(reloaded["external_agents"]["claude_code"]["approval_mode"], "auto") self.assertEqual(reloaded["external_agents"]["codex"]["approval_mode"], "auto") def test_load_migrates_legacy_opencode_default_model(self) -> None: with _workspace_tempdir() as config_dir: agent_config_path = config_dir / "agent_config.yaml" agent_config_path.write_text( yaml.dump( { "external_agents": { "preferred_order": ["opencode"], "opencode": { "command": "opencode", "model": "opencode/minimax-m2.5-free", "model_flag": "--model", }, } } ), encoding="utf-8", ) config = OPCConfig.load(config_dir) self.assertEqual(config.agents.agents["opencode"].model, "") reloaded = yaml.safe_load(agent_config_path.read_text(encoding="utf-8")) self.assertEqual(reloaded["external_agents"]["opencode"]["model"], "") def _build_task_mode_engine(self) -> OPCEngine: engine = OPCEngine(config=OPCConfig(), project_id="proj1") engine.secretary_policies = None engine.org_engine = SimpleNamespace( configure_task_mode_tools=MagicMock(), get_task_mode_role=MagicMock(return_value=SimpleNamespace(role_id=TASK_MODE_GENERAL_ROLE_ID)), current_org_version=MagicMock(return_value=1), current_runtime_topology_version=MagicMock(return_value=1), ) engine.task_scheduler = SimpleNamespace( create_tasks=AsyncMock( return_value=[ Task( id="task-1", title="Task mode execution", assigned_to=TASK_MODE_GENERAL_ROLE_ID, project_id="proj1", session_id="sess-1", metadata={}, ) ] ) ) engine.store = SimpleNamespace(save_task=AsyncMock()) engine.memory = None engine._resolve_workspace_contract = AsyncMock( # type: ignore[method-assign] return_value={ "workspace_root": "/tmp/task-mode-workspace", "output_root": "", "comms_workspace_root": "/tmp/task-mode-workspace", "comms_root": "/tmp/task-mode-workspace/.opc-comms", } ) engine._execute_single_agent = AsyncMock(return_value="executed") # type: ignore[method-assign] engine._build_attachment_context = MagicMock(return_value="") # type: ignore[method-assign] engine._normalize_attachment_refs = MagicMock(return_value=[]) # type: ignore[method-assign] engine._requests_explicit_project_knowledge = MagicMock(return_value=False) # type: ignore[method-assign] engine._task_mode_tool_names = MagicMock( # type: ignore[method-assign] return_value=["request_user_input", "shell_exec", "browser_navigate", "agent_spawn"] ) return engine async def test_company_followup_keeps_explicit_native_override(self) -> None: engine = OPCEngine() decision = RouterDecision( mode=ExecutionMode.COMPANY_MODE, preferred_agent="native", domains=[], company_profile="corporate", ) updated = engine._apply_session_execution_defaults( decision, { "mode": ExecutionMode.COMPANY_MODE.value, "company_profile": "corporate", "preferred_agent": "codex", }, "继续修这个 company mode 任务", ) self.assertEqual(updated.mode, ExecutionMode.COMPANY_MODE) self.assertEqual(updated.preferred_agent, "native") async def test_task_mode_defaults_to_native_main_agent(self) -> None: engine = self._build_task_mode_engine() result = await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[]), "Open the workspace and investigate the issue.", session_id="sess-1", ) self.assertEqual(result, "executed") created = engine.task_scheduler.create_tasks.await_args.args[0][0] self.assertEqual(created["assigned_to"], TASK_MODE_GENERAL_ROLE_ID) self.assertIsNone(created["assigned_external_agent"]) self.assertTrue(created["metadata"]["force_native_execution"]) self.assertEqual(created["metadata"]["work_item_execution_strategy"], WorkItemExecutionStrategy.NATIVE.value) self.assertEqual(created["metadata"]["task_mode_contract"], "single_full_capability_main_agent") self.assertEqual(created["metadata"]["runtime_kind"], "task_mode_agent_turn") self.assertNotIn("work_item_projection_id", created["metadata"]) self.assertNotIn("work_item_turn_type", created["metadata"]) self.assertNotIn("work_item_metadata", created["metadata"]) self.assertNotIn("employee_assignment", created["metadata"]) self.assertNotIn("employee_prompt_context", created["metadata"]) self.assertNotIn("employee_delta_context", created["metadata"]) self.assertIsNone(engine._execute_single_agent.await_args.args[1]) async def test_task_mode_preserves_explicit_external_override(self) -> None: engine = self._build_task_mode_engine() result = await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[], preferred_agent="codex"), "Implement the feature with the external coding agent.", session_id="sess-1", ) self.assertEqual(result, "executed") created = engine.task_scheduler.create_tasks.await_args.args[0][0] self.assertEqual(created["assigned_to"], TASK_MODE_GENERAL_ROLE_ID) self.assertEqual(created["assigned_external_agent"], "codex") self.assertFalse(created["metadata"]["force_native_execution"]) self.assertEqual(created["metadata"]["preferred_external_agent"], "codex") self.assertEqual(created["metadata"]["work_item_execution_strategy"], WorkItemExecutionStrategy.EXTERNAL.value) self.assertNotIn("work_item_projection_id", created["metadata"]) self.assertNotIn("work_item_turn_type", created["metadata"]) self.assertNotIn("employee_assignment", created["metadata"]) self.assertEqual(engine._execute_single_agent.await_args.args[1], "codex") async def test_company_root_task_uses_selected_agent_over_template_preference(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") engine.store = SimpleNamespace( get_runtime_task_for_work_item=AsyncMock(return_value=None), save_delegation_work_item=AsyncMock(), save_task=AsyncMock(), link_work_item_runtime_task=AsyncMock(return_value=True), ) engine.memory = SimpleNamespace(ensure_session=AsyncMock()) engine.org_engine = SimpleNamespace( current_org_version=MagicMock(return_value=1), current_runtime_topology_version=MagicMock(return_value=1), ) engine._requests_explicit_project_knowledge = MagicMock(return_value=False) # type: ignore[method-assign] work_item = DelegationWorkItem( work_item_id="wi-opencode", run_id="run-1", cell_id="team::engineering", team_instance_id="team-instance-1", role_id="engineer", seat_id="seat-engineer", title="Engineering execution", summary="Implement the requested change.", kind="execute", projection_id="engineering-execute", metadata={ "seat_id": "seat-engineer", "team_id": "team::engineering", "work_kind": "execute", }, ) runtime_topology = { "final_decider_role_id": "lead", "seats": [ { "seat_id": "seat-engineer", "team_id": "team::engineering", "role_id": "engineer", "preferred_external_agent": "claude_code", "selected_execution_agent": "opencode", "execution_agent_locked": True, "employee_assignment": {"employee_id": "eng-1", "name": "Engineer"}, "metadata": {"role_name": "Engineer"}, } ], } task = await engine._ensure_runtime_work_item_task( work_item=work_item, parent_session_id="sess-company", original_message="Build the thing.", decision=RouterDecision(mode=ExecutionMode.COMPANY_MODE, domains=[], company_profile="corporate"), runtime_topology=runtime_topology, delegation_playbook={}, secretary_context="", target_output_dir=None, origin_channel="cli", origin_chat_id="", origin_thread_id="", origin_task_id=None, attachment_refs=[], attachment_context="", force_native_execution=False, ) self.assertEqual(task.assigned_external_agent, "opencode") self.assertEqual(task.metadata["selected_execution_agent"], "opencode") self.assertEqual(task.metadata["preferred_external_agent"], "opencode") self.assertEqual(task.metadata["work_item_execution_strategy"], WorkItemExecutionStrategy.EXTERNAL.value) async def test_company_materialized_work_item_uses_selected_agent_over_template_preference(self) -> None: saved_tasks: list[Task] = [] async def save_task(task: Task) -> None: saved_tasks.append(task) executor = CompanyWorkItemExecutor( org_engine=SimpleNamespace(), communication=SimpleNamespace(), approval_engine=SimpleNamespace(), memory=SimpleNamespace(ensure_session=AsyncMock()), execute_task=AsyncMock(), save_task=AsyncMock(), ) executor.store = SimpleNamespace( get_runtime_task_for_work_item=AsyncMock(return_value=None), save_delegation_work_item=AsyncMock(), save_task=AsyncMock(side_effect=save_task), link_work_item_runtime_task=AsyncMock(return_value=True), ) root_task = Task( id="root-task", title="Root", project_id="proj1", session_id="sess-company", metadata={ "execution_mode": "company_mode", "runtime_model": "multi_team_org", "runtime_topology": { "seats": [ { "seat_id": "seat-engineer", "team_id": "team::engineering", "role_id": "engineer", "preferred_external_agent": "claude_code", "selected_execution_agent": "cursor", "execution_agent_locked": True, "metadata": {"role_name": "Engineer"}, } ] }, }, ) work_item = DelegationWorkItem( work_item_id="wi-cursor", run_id="run-1", cell_id="team::engineering", team_instance_id="team-instance-1", role_id="engineer", seat_id="seat-engineer", title="Engineering execution", summary="Implement the requested change.", kind="execute", projection_id="engineering-execute", metadata={"seat_id": "seat-engineer", "team_id": "team::engineering"}, ) tasks = await executor._materialize_work_item_tasks([root_task], [work_item]) created = next(task for task in tasks if task.id != root_task.id) self.assertEqual(created.assigned_external_agent, "cursor") self.assertEqual(created.metadata["selected_execution_agent"], "cursor") self.assertEqual(created.metadata["preferred_external_agent"], "cursor") self.assertEqual(created.metadata["work_item_execution_strategy"], WorkItemExecutionStrategy.EXTERNAL.value) self.assertEqual(saved_tasks[0].assigned_external_agent, "cursor") async def test_task_mode_external_followup_reuses_primary_session_external_agent_session(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") engine.store = SimpleNamespace( get_external_session=AsyncMock( return_value=SimpleNamespace( agent_type="claude_code", project_id="proj1", session_id="4f8b0b4e-6ab0-4ac5-9cf6-d7f0b4fbc001", task_id="previous-task", metadata={}, ) ) ) task = Task( id="task-followup", project_id="proj1", session_id="sess-1", assigned_external_agent="claude_code", metadata={ "mode": "task", "task_mode_contract": "single_full_capability_main_agent", }, ) adapter = ClaudeCodeAdapter(config=ExternalAgentConfig(command="claude")) run_adapter, resume_metadata = await engine._configure_external_adapter_for_task(task, adapter) self.assertEqual(run_adapter.config.session_mode, "resume") self.assertEqual(run_adapter.config.session_id, "4f8b0b4e-6ab0-4ac5-9cf6-d7f0b4fbc001") engine.store.get_external_session.assert_awaited_once_with("claude_code", "proj1", opc_session_id="sess-1") self.assertEqual(resume_metadata["resume_source_session"], "4f8b0b4e-6ab0-4ac5-9cf6-d7f0b4fbc001") async def test_task_mode_followup_reuses_existing_primary_task(self) -> None: engine = self._build_task_mode_engine() existing = Task( id="task-existing", title="Task mode execution", description="old message", assigned_to=TASK_MODE_GENERAL_ROLE_ID, project_id="proj1", session_id="sess-1", status=TaskStatus.IDLE, metadata={ "mode": "task", "task_mode_contract": "single_full_capability_main_agent", "work_item_projection_id": "task_mode_execution", "work_item_turn_type": "execute", "work_item_metadata": {"legacy": True}, "employee_assignment": {"employee_id": "task-general-default-employee"}, }, ) engine.store = SimpleNamespace( save_task=AsyncMock(), get_task=AsyncMock(return_value=existing), get_tasks=AsyncMock(return_value=[existing]), ) engine._execute_single_agent = AsyncMock(return_value="executed") # type: ignore[method-assign] result = await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[]), "Follow-up request", session_id="sess-1", origin_task_id="task-existing", ) self.assertEqual(result, "executed") engine.task_scheduler.create_tasks.assert_not_awaited() reused = engine._execute_single_agent.await_args.args[0][0] self.assertEqual(reused.id, "task-existing") self.assertEqual(reused.title, "Follow-up request") self.assertEqual(reused.description, "Follow-up request") self.assertEqual(reused.metadata["execution_task_ids"], ["task-existing"]) self.assertEqual(reused.metadata["runtime_kind"], "task_mode_agent_turn") self.assertNotIn("work_item_projection_id", reused.metadata) self.assertNotIn("work_item_turn_type", reused.metadata) self.assertNotIn("work_item_metadata", reused.metadata) self.assertNotIn("employee_assignment", reused.metadata) self.assertNotIn("employee_prompt_context", reused.metadata) self.assertNotIn("employee_delta_context", reused.metadata) async def test_task_mode_reused_task_updates_current_conversation_turn_id(self) -> None: engine = self._build_task_mode_engine() existing = Task( id="task-existing-turn", title="Task mode execution", description="old message", assigned_to=TASK_MODE_GENERAL_ROLE_ID, project_id="proj1", session_id="sess-1", status=TaskStatus.IDLE, metadata={ "mode": "task", "task_mode_contract": "single_full_capability_main_agent", "employee_assignment": {"employee_id": "task-general-default-employee"}, "runtime_v2": {"runtime_session_id": "rt_existing"}, }, ) engine.store = SimpleNamespace( save_task=AsyncMock(), get_task=AsyncMock(return_value=existing), get_tasks=AsyncMock(return_value=[existing]), ) engine._execute_single_agent = AsyncMock(return_value="executed") # type: ignore[method-assign] await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[]), "First follow-up", session_id="sess-1", origin_task_id="task-existing-turn", conversation_turn_id="ui-turn:first", ) first_reused = engine._execute_single_agent.await_args.args[0][0] self.assertEqual(first_reused.metadata["conversation_turn_id"], "ui-turn:first") self.assertEqual(first_reused.metadata["current_turn_id"], "ui-turn:first") self.assertEqual(first_reused.metadata["runtime_v2"]["current_turn_id"], "ui-turn:first") self.assertNotIn("employee_assignment", first_reused.metadata) engine._execute_single_agent.reset_mock() await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[]), "Second follow-up", session_id="sess-1", origin_task_id="task-existing-turn", conversation_turn_id="ui-turn:second", ) second_reused = engine._execute_single_agent.await_args.args[0][0] self.assertEqual(second_reused.metadata["conversation_turn_id"], "ui-turn:second") self.assertEqual(second_reused.metadata["current_turn_id"], "ui-turn:second") self.assertEqual(second_reused.metadata["runtime_v2"]["current_turn_id"], "ui-turn:second") async def test_task_mode_first_native_turn_reuses_office_ui_shell_task(self) -> None: engine = self._build_task_mode_engine() existing = Task( id="ui-shell-task", title="New Chat", description="", assigned_to="", project_id="proj1", session_id="sess-1", status=TaskStatus.RUNNING, metadata={ "exec_mode": "task", "execution_mode": "task_mode", "preferred_agent": "native", "origin_task_id": "ui-shell-task", }, ) engine.store = SimpleNamespace( save_task=AsyncMock(), get_task=AsyncMock(return_value=existing), get_tasks=AsyncMock(return_value=[existing]), ) engine._execute_single_agent = AsyncMock(return_value="executed") # type: ignore[method-assign] result = await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[], preferred_agent="native"), "First native request", session_id="sess-1", origin_task_id="ui-shell-task", ) self.assertEqual(result, "executed") engine.task_scheduler.create_tasks.assert_not_awaited() reused = engine._execute_single_agent.await_args.args[0][0] self.assertEqual(reused.id, "ui-shell-task") self.assertEqual(reused.assigned_to, TASK_MODE_GENERAL_ROLE_ID) self.assertEqual(reused.metadata["mode"], "task") self.assertEqual(reused.metadata["origin_task_id"], "ui-shell-task") self.assertEqual(reused.metadata["task_mode_contract"], "single_full_capability_main_agent") self.assertEqual(reused.metadata["selected_execution_agent"], "native") self.assertNotIn("employee_assignment", reused.metadata) async def test_runtime_v2_assistant_turn_suppresses_top_level_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") runtime_message = SessionMessageRecord( session_id="sess-1", role="assistant", task_id="ui-shell-task", agent_id=TASK_MODE_GENERAL_ROLE_ID, metadata={"kind": "runtime_v2_assistant"}, ) runtime_part = SessionPartRecord( session_id="sess-1", message_id=runtime_message.message_id, part_type="text", payload={"text": "Done.\n\nVerification: not required."}, ) engine.store = SimpleNamespace( get_session_transcript=AsyncMock( return_value=[{"message": runtime_message, "parts": [runtime_part]}] ) ) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-1", "please do it", "Done.", mode="task", origin_task_id="ui-shell-task", ) engine.memory.record_assistant_turn.assert_not_awaited() async def test_native_task_mode_primary_exchange_does_not_synthesize_runtime_fallback(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") engine.store = SimpleNamespace(get_session_transcript=AsyncMock(return_value=[])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-1", "please do it", "Done.", mode="task", origin_task_id="ui-shell-task", preferred_agent="native", ) engine.memory.record_assistant_turn.assert_not_awaited() async def test_external_task_mode_primary_exchange_records_opc_wrapped_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") engine.store = SimpleNamespace(get_session_transcript=AsyncMock(return_value=[])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-1", "please do it", "External result.", mode="task", origin_task_id="ui-shell-task", preferred_agent="codex", ) engine.memory.record_assistant_turn.assert_awaited_once() _, kwargs = engine.memory.record_assistant_turn.await_args self.assertEqual(kwargs["content"], "External result.") self.assertEqual(kwargs["metadata"], {"kind": "top_level_reply"}) async def test_external_task_mode_primary_exchange_records_when_preferred_agent_missing(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") external_task = Task( id="runtime-task", session_id="sess-1", project_id="proj1", assigned_external_agent="opencode", result={"content": "External result.", "artifacts": {}}, metadata={ "mode": "task", "task_mode_contract": "single_full_capability_main_agent", "origin_task_id": "ui-shell-task", "selected_execution_agent": "opencode", }, ) engine.store = SimpleNamespace( get_tasks=AsyncMock(return_value=[external_task]), get_session_transcript=AsyncMock(return_value=[]), ) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-1", "please do it", "External result.", mode="task", origin_task_id="ui-shell-task", ) engine.memory.record_assistant_turn.assert_awaited_once() _, kwargs = engine.memory.record_assistant_turn.await_args self.assertEqual(kwargs["content"], "External result.") self.assertEqual(kwargs["metadata"], {"kind": "top_level_reply"}) async def test_external_task_mode_done_result_records_top_level_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") task = Task( id="runtime-task", session_id="sess-1", project_id="proj1", assigned_external_agent="opencode", metadata={ "mode": "task", "execution_mode": "task_mode", "task_mode_contract": "single_full_capability_main_agent", "selected_execution_agent": "opencode", "conversation_turn_id": "ui-turn:external", }, ) engine.store = SimpleNamespace(get_session_transcript=AsyncMock(return_value=[])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_task_mode_external_result_reply(task, "External result.") engine.memory.record_assistant_turn.assert_awaited_once() _, kwargs = engine.memory.record_assistant_turn.await_args self.assertEqual(kwargs["session_id"], "sess-1") self.assertEqual(kwargs["content"], "External result.") self.assertEqual(kwargs["task_id"], "runtime-task") self.assertEqual(kwargs["metadata"]["kind"], "top_level_reply") self.assertTrue(kwargs["metadata"]["task_mode_external_result"]) self.assertEqual(kwargs["metadata"]["assigned_external_agent"], "opencode") self.assertEqual(kwargs["metadata"]["conversation_turn_id"], "ui-turn:external") self.assertEqual(kwargs["metadata"]["ui_message_id"], "task-mode-external-reply:ui-turn:external") async def test_external_task_mode_done_result_reply_is_idempotent(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") task = Task( id="runtime-task", session_id="sess-1", project_id="proj1", assigned_external_agent="claude_code", metadata={ "mode": "task", "execution_mode": "task_mode", "task_mode_contract": "single_full_capability_main_agent", "conversation_turn_id": "ui-turn:external", }, ) message = SessionMessageRecord( session_id="sess-1", role="assistant", task_id="runtime-task", metadata={ "kind": "top_level_reply", "task_mode_external_result": True, "task_id": "runtime-task", "conversation_turn_id": "ui-turn:external", }, ) part = SessionPartRecord( session_id="sess-1", message_id=message.message_id, part_type="text", payload={"text": "External result."}, ) engine.store = SimpleNamespace( get_session_transcript=AsyncMock(return_value=[{"message": message, "parts": [part]}]) ) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_task_mode_external_result_reply(task, "External result.") engine.memory.record_assistant_turn.assert_not_awaited() async def test_company_work_item_external_result_does_not_record_task_mode_top_level_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") task = Task( id="company-runtime-task", session_id="sess-company", project_id="proj1", assigned_external_agent="opencode", metadata={ "mode": "task", "execution_mode": "company_mode", "company_profile": "corporate", "work_item_projection_id": "ceo::delivery", "selected_execution_agent": "opencode", }, ) engine.store = SimpleNamespace(get_session_transcript=AsyncMock(return_value=[])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_task_mode_external_result_reply(task, "Company role result.") engine.memory.record_assistant_turn.assert_not_awaited() async def test_existing_company_runtime_early_reply_records_assistant_turn(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") engine.context_loader = SimpleNamespace() engine.memory = SimpleNamespace( record_user_turn=AsyncMock(), record_assistant_turn=AsyncMock(), ) engine._ensure_primary_session = AsyncMock() engine._maybe_resume_checkpoint = AsyncMock(return_value=None) engine._maybe_handle_reorg_message = AsyncMock(return_value=None) engine._maybe_resume_existing_company_runtime = AsyncMock( return_value="Routed the latest user follow-up to the final decider." ) response = await engine._handle_message(UserMessage( channel="cli", user_id="owner", content="检查一下UI", session_id="sess-company", metadata={ "mode": "company", "origin_task_id": "task-company", "company_profile": "corporate", }, )) self.assertEqual(response.content, "Routed the latest user follow-up to the final decider.") engine.memory.record_user_turn.assert_awaited_once() engine.memory.record_assistant_turn.assert_awaited_once() _, kwargs = engine.memory.record_assistant_turn.await_args self.assertEqual(kwargs["session_id"], "sess-company") self.assertEqual(kwargs["content"], response.content) self.assertEqual(kwargs["metadata"], {"kind": "top_level_reply"}) async def test_company_internal_dispatch_result_does_not_synthesize_top_level_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") dispatch_text = "已创建下游 WorkItem:`wi-ppt`,交给 `report_producer`。" intake_task = Task( id="intake-task", session_id="sess-company", status=TaskStatus.DONE, result={"content": dispatch_text, "artifacts": {}}, metadata={ "execution_model": "multi_team_org", "work_item_turn_type": "intake", "manager_board_mutation_performed": True, "delegation_wait_for_work_item_ids": ["wi-ppt"], }, ) engine.store = SimpleNamespace(get_tasks=AsyncMock(return_value=[intake_task])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-company", "生成一个 PPT", dispatch_text, mode="company", origin_task_id="ui-anchor", ) engine.memory.record_assistant_turn.assert_not_awaited() async def test_company_final_delivery_result_does_not_synthesize_top_level_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") final_text = "最终交付内容已经由最高层角色给出,包含可直接展示给用户的结论。" delivery_task = Task( id="delivery-task", session_id="sess-company:delivery", parent_session_id="sess-company", status=TaskStatus.AWAITING_HUMAN, result={"content": final_text, "artifacts": {}}, metadata={ "execution_mode": ExecutionMode.COMPANY_MODE.value, "work_item_turn_type": "deliver", "feedback_scope": "final", "authoritative_output": True, "company_profile": "custom", }, ) engine.store = SimpleNamespace(get_tasks=AsyncMock(return_value=[delivery_task])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-company", "做一个最终分析", final_text, mode="company", origin_task_id="ui-anchor", ) engine.memory.record_assistant_turn.assert_not_awaited() async def test_non_company_marker_text_still_records_top_level_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") dispatch_text = "已创建下游 WorkItem 是 company mode 的内部进度文案。" engine.store = SimpleNamespace(get_tasks=AsyncMock(return_value=[])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-chat", "解释这句话", dispatch_text, mode="chat", origin_task_id="ui-anchor", preferred_agent="codex", ) engine.memory.record_assistant_turn.assert_awaited_once() _, kwargs = engine.memory.record_assistant_turn.await_args self.assertEqual(kwargs["content"], dispatch_text) async def test_company_marker_text_without_internal_task_still_records_direct_reply(self) -> None: engine = OPCEngine(config=OPCConfig(), project_id="proj1") dispatch_text = "已创建下游 WorkItem 这句话可以作为最高层显式说明的一部分。" engine.store = SimpleNamespace(get_tasks=AsyncMock(return_value=[])) engine.memory = SimpleNamespace(record_assistant_turn=AsyncMock()) await engine._record_primary_exchange( "sess-company", "解释当前状态", dispatch_text, mode="company", origin_task_id="ui-anchor", ) engine.memory.record_assistant_turn.assert_awaited_once() _, kwargs = engine.memory.record_assistant_turn.await_args self.assertEqual(kwargs["content"], dispatch_text) async def test_task_mode_followup_restores_runtime_resume_on_reused_task(self) -> None: engine = self._build_task_mode_engine() existing = Task( id="task-existing-runtime", title="Task mode execution", description="old message", assigned_to=TASK_MODE_GENERAL_ROLE_ID, project_id="proj1", session_id="sess-1", metadata={ "mode": "task", "task_mode_contract": "single_full_capability_main_agent", "employee_assignment": {"employee_id": "task-general-default-employee"}, "runtime_v2": {"runtime_session_id": "rt_existing", "task_ledger": [{"content": "keep going"}]}, "_runtime_v2_user_seeded": True, }, ) engine.store = SimpleNamespace( save_task=AsyncMock(), get_task=AsyncMock(return_value=existing), get_tasks=AsyncMock(return_value=[existing]), ) engine._execute_single_agent = AsyncMock(return_value="executed") # type: ignore[method-assign] await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[]), "Another turn", session_id="sess-1", origin_task_id="task-existing-runtime", ) reused = engine._execute_single_agent.await_args.args[0][0] self.assertEqual(reused.context_snapshot["runtime_resume"]["runtime_session_id"], "rt_existing") self.assertNotIn("_runtime_v2_user_seeded", reused.metadata) self.assertNotIn("employee_assignment", reused.metadata) async def test_task_mode_followup_can_reuse_existing_task_by_session_without_origin_task_id(self) -> None: engine = self._build_task_mode_engine() existing = Task( id="task-existing-by-session", title="Task mode execution", description="old message", assigned_to=TASK_MODE_GENERAL_ROLE_ID, project_id="proj1", session_id="sess-1", status=TaskStatus.IDLE, metadata={ "mode": "task", "task_mode_contract": "single_full_capability_main_agent", "employee_assignment": {"employee_id": "task-general-default-employee"}, "runtime_v2": {"runtime_session_id": "rt_session"}, }, ) engine.store = SimpleNamespace( save_task=AsyncMock(), get_task=AsyncMock(return_value=None), get_tasks=AsyncMock(return_value=[existing]), ) engine._execute_single_agent = AsyncMock(return_value="executed") # type: ignore[method-assign] await engine._continue_task_mode_execution( RouterDecision(mode=ExecutionMode.TASK_MODE, domains=[]), "Session-only follow-up", session_id="sess-1", ) reused = engine._execute_single_agent.await_args.args[0][0] self.assertEqual(reused.id, "task-existing-by-session") self.assertEqual(reused.context_snapshot["runtime_resume"]["runtime_session_id"], "rt_session") self.assertNotIn("employee_assignment", reused.metadata) async def test_explicit_native_forces_native_selection(self) -> None: engine = OPCEngine(config=OPCConfig()) engine.org_engine = SimpleNamespace() task = Task( title="Implement feature", assigned_to="executor", metadata={"router_preferred_agent": "native"}, ) selected = await engine._assign_task_execution_agent(task) self.assertIsNone(selected) self.assertIsNone(task.assigned_external_agent) self.assertEqual(task.metadata["agent_selection"]["selection_source"], "forced_native") async def test_runtime_refresh_updates_timeout_and_reloads_dependencies(self) -> None: with _workspace_tempdir() as opc_home: config_dir = opc_home / "config" config_dir.mkdir(parents=True, exist_ok=True) (config_dir / "system_config.yaml").write_text( yaml.safe_dump( { "system": { "task_mode": { "sub_agent_timeout_sec": 86400, } } }, sort_keys=False, ), encoding="utf-8", ) engine = OPCEngine(config=OPCConfig(), opc_home=opc_home) engine.company_executor = SimpleNamespace(work_item_timeout=3600) engine.approval_engine = SimpleNamespace(config=engine.config.autonomy) engine.adapter_registry = SimpleNamespace(config=None, initialize=AsyncMock()) engine.org_engine = SimpleNamespace( config=None, reload_from_config=MagicMock(), configure_task_mode_tools=MagicMock(), ) await engine._refresh_runtime_config_from_disk() self.assertEqual(engine.company_executor.work_item_timeout, 86400) engine.adapter_registry.initialize.assert_awaited_once() engine.org_engine.reload_from_config.assert_called_once() engine.org_engine.configure_task_mode_tools.assert_called_once() async def test_auto_mode_keeps_external_agents_for_complex_work(self) -> None: engine = OPCEngine(config=OPCConfig()) engine.org_engine = SimpleNamespace() engine._available_external_agents = lambda: ["codex"] # type: ignore[method-assign] role = SimpleNamespace( role_id="executor", name="Executor", responsibility="Engineering implementation, file edits, shell work, and system delivery.", preferred_external_agent="codex", runtime_policy={"execution_strategy": "auto", "default_turn_type": "work"}, ) task = Task( title="Implement backend API", description="Implement the API, edit files, run tests, and update the repository artifacts.", assigned_to="executor", metadata={}, ) selected = await engine._assign_task_execution_agent(task, role=role) self.assertEqual(selected, "codex") self.assertEqual(task.assigned_external_agent, "codex") self.assertEqual(task.metadata["agent_selection"]["selection_source"], "fallback_rules") class AdapterRegistryConfigTests(unittest.IsolatedAsyncioTestCase): async def test_disabled_agents_are_not_marked_available(self) -> None: config = AgentsConfig() for agent_config in config.agents.values(): agent_config.enabled = False registry = AdapterRegistry(config) patches = [ patch.object(ClaudeCodeAdapter, "is_available", AsyncMock(return_value=True)), patch.object(CursorAdapter, "is_available", AsyncMock(return_value=True)), patch.object(CodexAdapter, "is_available", AsyncMock(return_value=True)), patch.object(OpenCodeAdapter, "is_available", AsyncMock(return_value=True)), ] with patches[0], patches[1], patches[2], patches[3]: await registry.initialize() self.assertEqual(registry.list_available(), []) self.assertIsNone(registry.get("codex")) if __name__ == "__main__": unittest.main()