feat: harden company resume, external sessions, and Office UI i18n

Improve suspend/resume identity continuity and external broker session handling, add Office UI internationalization with rebuilt assets, and expand company org configs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
小东
2026-07-16 09:20:40 +08:00
parent eb934cf2bd
commit cb6b8ed6e1
32 changed files with 2562 additions and 446 deletions
+141
View File
@@ -21,6 +21,7 @@ from __future__ import annotations
import tempfile
import unittest
from datetime import datetime, timedelta
from pathlib import Path
from types import SimpleNamespace
@@ -37,6 +38,7 @@ from opc.core.models import (
from opc.database.store import OPCStore
from opc.layer3_agent.adapters.base import ExternalAgentAdapter
from opc.layer3_agent.external_broker import ExternalAgentBroker
from opc.layer3_agent.external_session_identity import select_best_external_resume_session
# ── Store helper unit tests ──────────────────────────────────────────────
@@ -97,9 +99,28 @@ class AdapterSessionStateHelperTests(unittest.IsolatedAsyncioTestCase):
await self.store.update_role_session_adapter_state(
self.sid, "codex", {"resume_session_id": "thread-abc"},
)
role_session = await self.store.get_delegation_role_session(self.sid)
role_session.adapter_session_state.update({
"selected_execution_agent": "codex",
"external_resume_session_id": "thread-abc",
"external_resume_session_scope_id": "sess-a",
"external_resume_agent_type": "codex",
"resume_source_session": "thread-abc",
})
await self.store.save_delegation_role_session(role_session)
await self.store.update_role_session_adapter_state(self.sid, "codex", None)
entry = await self.store.get_role_session_adapter_state(self.sid, "codex")
refreshed = await self.store.get_delegation_role_session(self.sid)
self.assertIsNone(entry)
self.assertEqual(
refreshed.adapter_session_state.get("selected_execution_agent"),
"codex",
)
self.assertNotIn(
"external_resume_session_id",
refreshed.adapter_session_state,
)
self.assertNotIn("resume_source_session", refreshed.adapter_session_state)
async def test_missing_session_returns_false(self) -> None:
ok = await self.store.update_role_session_adapter_state(
@@ -245,6 +266,49 @@ class BrokerPersistWritesRoleStateTests(unittest.IsolatedAsyncioTestCase):
)
self.assertIsNone(entry)
async def test_failed_resume_clears_matching_older_role_token_and_task_metadata(self) -> None:
await self._persist_run(
task_id="task-old",
agent_type="codex",
resume_session_id="thread-full",
)
adapter = _MiniAdapter(agent_type="codex")
adapter.config.session_mode = "resume"
adapter.config.session_id = "thread-full"
task = self._task(task_id="task-retry")
task.metadata.update({
"external_resume_session_id": "thread-full",
"external_resume_session_scope_id": "sess-a",
"external_resume_agent_type": "codex",
})
await self.broker._persist_session(
adapter=adapter,
task=task,
workspace_path="/tmp/ws",
metadata={
"command": "codex exec resume",
"model": "(cli default)",
"resume_session_id": "thread-full",
},
result=TaskResult(
status=TaskStatus.FAILED,
content="context window full",
artifacts={"resume_session_id": "thread-full"},
),
)
self.assertIsNone(
await self.store.get_role_session_adapter_state(
self.role_session_id,
"codex",
)
)
self.assertNotIn("external_resume_session_id", task.metadata)
self.assertEqual(
task.metadata.get("external_resume_fallback"),
"provider_terminal_failure",
)
async def test_consecutive_tasks_overwrite_with_latest_token(self) -> None:
await self._persist_run(
task_id="task-1", agent_type="codex", resume_session_id="thread-1",
@@ -258,6 +322,34 @@ class BrokerPersistWritesRoleStateTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(entry["resume_session_id"], "thread-2")
self.assertEqual(entry["last_task_id"], "task-2")
def test_newer_failed_row_vetoes_older_done_row_for_same_token(self) -> None:
now = datetime.now()
older = ExternalSession(
agent_type="codex",
project_id="proj1",
session_id="thread-full",
status="done",
metadata={"resume_session_id": "thread-full"},
updated_at=now - timedelta(minutes=1),
)
newer = ExternalSession(
agent_type="codex",
project_id="proj1",
session_id="thread-full",
status="failed",
metadata={"resume_session_id": "thread-full"},
updated_at=now,
)
selected, token = select_best_external_resume_session(
[older, newer],
agent_type="codex",
project_id="proj1",
)
self.assertIsNone(selected)
self.assertEqual(token, "")
async def test_per_agent_isolation_codex_claude_opencode(self) -> None:
# All three external adapters land in the same broker path. Each
# gets its own slot under adapter_session_state keyed by agent_type.
@@ -430,6 +522,55 @@ class BrokerRestorePrefersRoleStateTests(unittest.IsolatedAsyncioTestCase):
"codex",
))
async def test_restore_rejects_terminal_normal_role_token_even_when_preconfigured(self) -> None:
token = "thread-terminal"
await self.store.update_role_session_adapter_state(
self.role_session_id,
"codex",
{
"resume_session_id": token,
"provider_session_id": token,
"last_task_id": "task-old",
},
)
await self.store.save_external_session(ExternalSession(
agent_type="codex",
project_id="proj1",
session_id=token,
opc_session_id=self.role_session_id,
task_id="task-failed",
workspace_path="/tmp/ws",
run_mode="exec",
status="failed",
metadata={
"resume_session_id": token,
"provider_session_id": token,
},
))
adapter = _MiniAdapter(agent_type="codex", can_resume_blank=False)
adapter.config.session_mode = "resume"
adapter.config.session_id = token
task = self._task()
task.metadata.update({
"external_resume_session_id": token,
"external_resume_session_scope_id": "sess-a",
"external_resume_agent_type": "codex",
})
await self.broker._restore_session_resume_from_store(adapter, task)
self.assertEqual(adapter.config.session_mode, "new")
self.assertEqual(adapter.config.session_id, "")
self.assertIsNone(await self.store.get_role_session_adapter_state(
self.role_session_id,
"codex",
))
self.assertNotIn("external_resume_session_id", task.metadata)
self.assertEqual(
task.metadata.get("external_resume_fallback"),
"provider_terminal_failure",
)
if __name__ == "__main__":
unittest.main()