Files
OpenOPC/tests/test_company_role_session_behavior.py
T
2026-07-14 20:31:10 +08:00

260 lines
10 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock
from opc.core.models import SessionMessageRecord, SessionRecord, Task, TaskResult, TaskStatus
from opc.engine import OPCEngine
from opc.layer5_memory.memory_manager import MemoryManager
class SharedRoleSessionIdTests(unittest.TestCase):
def test_final_decider_reuses_root_session(self) -> None:
self.assertEqual(
OPCEngine._shared_company_role_session_id(
"app14",
"ceo",
final_decider_role_id="ceo",
),
"app14",
)
def test_same_role_reuses_same_session_id(self) -> None:
first = OPCEngine._shared_company_role_session_id("app14", "cmo")
second = OPCEngine._shared_company_role_session_id("app14", "cmo")
self.assertEqual(first, "app14:role:cmo")
self.assertEqual(first, second)
class _MemoryStoreStub:
def __init__(self) -> None:
self.sessions: dict[str, SessionRecord] = {}
self.session_messages: list[SessionMessageRecord] = []
async def get_session(self, session_id: str) -> SessionRecord | None:
return self.sessions.get(session_id)
async def save_session(self, session: SessionRecord) -> None:
self.sessions[session.session_id] = session
async def save_session_link(self, _link: object) -> None:
return None
async def save_session_message(self, message: SessionMessageRecord) -> None:
self.session_messages.append(message)
async def save_session_part(self, _part: object) -> None:
return None
class MemoryManagerCompactionTests(unittest.IsolatedAsyncioTestCase):
async def test_append_session_message_does_not_trigger_history_compaction(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
memory = MemoryManager(Path(tmpdir), project_id="proj", store=_MemoryStoreStub())
compactor = SimpleNamespace(maybe_compact_after_message=AsyncMock())
memory.set_history_compactor(compactor)
await memory.append_session_message(
"sess-1",
"assistant",
text="hello",
project_id="proj",
metadata={"role_id": "cmo"},
)
compactor.maybe_compact_after_message.assert_not_awaited()
async def test_parent_child_result_preserves_structured_source_lineage(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
store = _MemoryStoreStub()
memory = MemoryManager(Path(tmpdir), project_id="0009", store=store)
task = Task(
id="task-cto-result",
title="CTO Result",
assigned_to="cto",
project_id="0009",
session_id="child-session",
parent_session_id="parent-session",
metadata={"work_item_projection_id": "cto::analysis"},
)
await memory.record_child_session_result(
"parent-session",
"child-session",
task=task,
result_content="done",
result_delivery_id=(
"result:task:task-cto-result:turn:canonical-cto-turn:attempt:0"
),
source_result_message_id="source-result-message",
canonical_turn_id="canonical-cto-turn",
)
metadata = store.session_messages[-1].metadata
self.assertEqual(
metadata["result_delivery_id"],
"result:task:task-cto-result:turn:canonical-cto-turn:attempt:0",
)
self.assertEqual(metadata["source_result_message_id"], "source-result-message")
self.assertEqual(metadata["source_task_id"], "task-cto-result")
self.assertEqual(metadata["child_session_id"], "child-session")
self.assertEqual(metadata["canonical_turn_id"], "canonical-cto-turn")
class SharedRoleSessionExecutionTests(unittest.IsolatedAsyncioTestCase):
def test_fallback_delivery_identity_is_per_result_not_per_provider_session(self) -> None:
task = Task(
id="shared-role-task",
title="Shared role",
assigned_to="cto",
retry_count=0,
)
first = TaskResult(
status=TaskStatus.AWAITING_HUMAN,
content="first pause",
artifacts={"provider_session_id": "provider-session-reused"},
)
second = TaskResult(
status=TaskStatus.DONE,
content="second result",
artifacts={"provider_session_id": "provider-session-reused"},
)
first_identity = OPCEngine._ensure_result_delivery_identity_for_commit(task, first)
second_identity = OPCEngine._ensure_result_delivery_identity_for_commit(task, second)
self.assertNotEqual(
first_identity["result_delivery_id"],
second_identity["result_delivery_id"],
)
self.assertNotEqual(
first.artifacts["result_execution_id"],
second.artifacts["result_execution_id"],
)
self.assertNotIn("provider-session-reused", first_identity["result_delivery_id"])
def test_fallback_delivery_identity_is_stable_after_result_persistence(self) -> None:
task = Task(id="external-task", title="External", assigned_to="cto")
result = TaskResult(
status=TaskStatus.DONE,
content="done",
artifacts={"runtime_session_id": "runtime-session-reused"},
)
first_identity = OPCEngine._ensure_result_delivery_identity_for_commit(task, result)
persisted_artifacts = dict(result.artifacts)
reloaded = TaskResult(
status=TaskStatus.DONE,
content="done",
artifacts=persisted_artifacts,
)
replay_identity = OPCEngine._ensure_result_delivery_identity_for_commit(task, reloaded)
self.assertEqual(replay_identity, first_identity)
self.assertEqual(reloaded.artifacts, persisted_artifacts)
async def test_company_shared_role_session_keeps_results_local(self) -> None:
engine = OPCEngine()
engine.store = SimpleNamespace(
get_task=AsyncMock(return_value=None),
save_task=AsyncMock(),
)
engine.memory = SimpleNamespace(
record_assistant_turn=AsyncMock(),
record_child_session_result=AsyncMock(),
record_task_completion_async=AsyncMock(),
)
engine._run_task_once = AsyncMock(
return_value=TaskResult(
status=TaskStatus.DONE,
content="done",
artifacts={"result_delivery_id": "delivery-shared-role"},
)
)
engine._apply_runtime_state_to_task = lambda task, result: None
task = Task(
id="task-cmo-review",
title="Review Turn: cmo",
assigned_to="cmo",
status=TaskStatus.PENDING,
project_id="new16",
session_id="app14:role:cmo",
parent_session_id="app14",
metadata={
"shared_role_session": True,
"execution_mode": "company_mode",
"work_item_projection_id": "review::demo",
"employee_assignment": {"employee_id": "emp-cmo", "role_id": "cmo"},
},
)
await engine._execute_task(task)
engine.memory.record_assistant_turn.assert_awaited_once()
engine.memory.record_child_session_result.assert_not_awaited()
metadata = engine.memory.record_assistant_turn.await_args.kwargs["metadata"]
self.assertEqual(metadata["result_delivery_id"], "delivery-shared-role")
self.assertEqual(metadata["source_task_id"], "task-cmo-review")
self.assertEqual(metadata["child_session_id"], "app14:role:cmo")
async def test_child_result_and_parent_mirror_share_delivery_identity(self) -> None:
engine = OPCEngine()
engine.store = SimpleNamespace(
get_task=AsyncMock(return_value=None),
save_task=AsyncMock(),
)
engine.memory = SimpleNamespace(
record_assistant_turn=AsyncMock(
return_value=SimpleNamespace(message_id="source-result-message")
),
record_child_session_result=AsyncMock(),
record_task_completion_async=AsyncMock(),
)
engine._run_task_once = AsyncMock(
return_value=TaskResult(
status=TaskStatus.DONE,
content="done",
artifacts={
"canonical_turn_id": "runtime-generated-turn",
"result_delivery_id": (
"result:task:task-cto-result:turn:runtime-generated-turn:attempt:0"
),
"runtime_session_id": "runtime-session-1",
},
)
)
engine._apply_runtime_state_to_task = lambda task, result: None
task = Task(
id="task-cto-result",
title="CTO Result",
assigned_to="cto",
status=TaskStatus.PENDING,
project_id="0009",
session_id="child-session",
parent_session_id="parent-session",
metadata={
"execution_mode": "company_mode",
"work_item_projection_id": "cto::analysis",
},
)
await engine._execute_task(task)
child_metadata = engine.memory.record_assistant_turn.await_args.kwargs["metadata"]
parent_call = engine.memory.record_child_session_result.await_args.kwargs
self.assertEqual(
child_metadata["result_delivery_id"],
"result:task:task-cto-result:turn:runtime-generated-turn:attempt:0",
)
self.assertEqual(
parent_call["result_delivery_id"],
child_metadata["result_delivery_id"],
)
self.assertEqual(parent_call["source_result_message_id"], "source-result-message")
self.assertEqual(parent_call["canonical_turn_id"], "runtime-generated-turn")