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:
@@ -517,7 +517,28 @@ class ExternalAgentAdapter(abc.ABC):
|
||||
merged = {**os.environ, **{str(k): str(v) for k, v in extra_env.items()}}
|
||||
path_value = merged.get("PATH") or merged.get("Path") or merged.get("path") or ""
|
||||
resolved = shutil.which(executable, path=path_value or None)
|
||||
if not resolved or resolved == executable:
|
||||
if not resolved:
|
||||
return cmd
|
||||
|
||||
if os.name == "nt":
|
||||
resolved = ExternalAgentAdapter._resolve_windows_command_shim(
|
||||
executable,
|
||||
resolved,
|
||||
path_value=path_value,
|
||||
)
|
||||
|
||||
if os.name == "nt" and os.path.splitext(resolved)[1].lower() in {".cmd", ".bat"}:
|
||||
comspec = str(os.environ.get("COMSPEC") or "").strip()
|
||||
if not comspec:
|
||||
comspec = shutil.which("cmd.exe") or "cmd.exe"
|
||||
wrapped_cmd = [comspec, "/d", "/s", "/c", resolved, *cmd[1:]]
|
||||
if isinstance(launch_metadata, dict):
|
||||
launch_metadata.setdefault("configured_binary", executable)
|
||||
launch_metadata["resolved_binary"] = resolved
|
||||
launch_metadata["launch_wrapper"] = comspec
|
||||
return wrapped_cmd
|
||||
|
||||
if resolved == executable:
|
||||
return cmd
|
||||
resolved_cmd = list(cmd)
|
||||
resolved_cmd[0] = resolved
|
||||
@@ -526,6 +547,31 @@ class ExternalAgentAdapter(abc.ABC):
|
||||
launch_metadata["resolved_binary"] = resolved
|
||||
return resolved_cmd
|
||||
|
||||
@staticmethod
|
||||
def _resolve_windows_command_shim(
|
||||
executable: str,
|
||||
resolved: str,
|
||||
*,
|
||||
path_value: str = "",
|
||||
) -> str:
|
||||
"""Prefer a Windows-launchable sibling over an extensionless POSIX shim."""
|
||||
if os.path.splitext(resolved)[1]:
|
||||
return resolved
|
||||
|
||||
for extension in (".exe", ".com", ".cmd", ".bat"):
|
||||
sibling = f"{resolved}{extension}"
|
||||
if os.path.isfile(sibling):
|
||||
return sibling
|
||||
|
||||
if os.path.dirname(executable):
|
||||
return resolved
|
||||
|
||||
for extension in (".exe", ".com", ".cmd", ".bat"):
|
||||
candidate = shutil.which(f"{executable}{extension}", path=path_value or None)
|
||||
if candidate:
|
||||
return candidate
|
||||
return resolved
|
||||
|
||||
async def send_process_input(
|
||||
self,
|
||||
proc: asyncio.subprocess.Process,
|
||||
|
||||
@@ -32,6 +32,8 @@ class CodexAdapter(ExternalAgentAdapter):
|
||||
"CODEX_SANDBOX_NETWORK_DISABLED",
|
||||
"CODEX_THREAD_ID",
|
||||
}
|
||||
_RUNTIME_SHELL_ENV_KEYS = {"PYTHONPATH"}
|
||||
_RUNTIME_SHELL_ENV_PREFIXES = ("OPC_",)
|
||||
_PROMPT_SENTINEL = "-"
|
||||
_TTY_EOF = b"\x04"
|
||||
_INTERACTIVE_ARGV_PROMPT_MAX_BYTES = 16 * 1024
|
||||
@@ -118,6 +120,64 @@ class CodexAdapter(ExternalAgentAdapter):
|
||||
normalized = key.upper()
|
||||
return normalized in cls._PARENT_CODEX_RUNTIME_ENV_VARS
|
||||
|
||||
@classmethod
|
||||
def _runtime_shell_environment_config_args(
|
||||
cls,
|
||||
extra_env: dict[str, str] | None,
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""Forward only OpenOPC's runtime contract into Codex shell tools.
|
||||
|
||||
Codex can intentionally run with ``shell_environment_policy.inherit=core``.
|
||||
The broker process still receives ``OPC_*`` variables, but commands launched
|
||||
by Codex do not unless they are added to the policy's ``set`` table.
|
||||
"""
|
||||
forwarded: dict[str, str] = {}
|
||||
for raw_key, raw_value in dict(extra_env or {}).items():
|
||||
key = str(raw_key).strip()
|
||||
if not key:
|
||||
continue
|
||||
if key not in cls._RUNTIME_SHELL_ENV_KEYS and not key.startswith(
|
||||
cls._RUNTIME_SHELL_ENV_PREFIXES
|
||||
):
|
||||
continue
|
||||
value = str(raw_value)
|
||||
if key == "OPC_ALLOWED_COLLAB_TOOLS":
|
||||
try:
|
||||
parsed = json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
parsed = None
|
||||
if isinstance(parsed, list):
|
||||
value = ",".join(
|
||||
str(item).strip() for item in parsed if str(item).strip()
|
||||
)
|
||||
forwarded[key] = value
|
||||
|
||||
args: list[str] = []
|
||||
for key in sorted(forwarded):
|
||||
value = forwarded[key]
|
||||
if "'''" in value:
|
||||
encoded = json.dumps(value, ensure_ascii=False)
|
||||
else:
|
||||
encoded = f"'''{value}'''"
|
||||
args.extend(["-c", f"shell_environment_policy.set.{key}={encoded}"])
|
||||
return args, sorted(forwarded)
|
||||
|
||||
@classmethod
|
||||
def _inject_runtime_shell_environment(
|
||||
cls,
|
||||
cmd: list[str],
|
||||
extra_env: dict[str, str] | None,
|
||||
launch_metadata: dict[str, Any] | None = None,
|
||||
) -> list[str]:
|
||||
config_args, forwarded_keys = cls._runtime_shell_environment_config_args(extra_env)
|
||||
if not config_args or not cmd:
|
||||
return list(cmd)
|
||||
insert_at = 2 if len(cmd) > 1 and str(cmd[1]).strip() == "exec" else 1
|
||||
launch_cmd = [*cmd[:insert_at], *config_args, *cmd[insert_at:]]
|
||||
if isinstance(launch_metadata, dict):
|
||||
launch_metadata["runtime_shell_environment_keys"] = forwarded_keys
|
||||
return launch_cmd
|
||||
|
||||
def build_workspace_args(self, workspace_path: str | None = None) -> list[str]:
|
||||
args: list[str] = []
|
||||
if workspace_path:
|
||||
@@ -564,8 +624,13 @@ class CodexAdapter(ExternalAgentAdapter):
|
||||
prompt_transport = str((launch_metadata or {}).get("prompt_transport") or "").strip().lower()
|
||||
prompt = self._prompt_text_from_task(task) if prompt_transport == "stdin" else ""
|
||||
env = self.build_process_env(extra_env)
|
||||
launch_cmd = self._resolve_launch_command(
|
||||
runtime_cmd = self._inject_runtime_shell_environment(
|
||||
cmd,
|
||||
extra_env,
|
||||
launch_metadata=launch_metadata,
|
||||
)
|
||||
launch_cmd = self._resolve_launch_command(
|
||||
runtime_cmd,
|
||||
extra_env=extra_env,
|
||||
launch_metadata=launch_metadata,
|
||||
)
|
||||
|
||||
@@ -92,11 +92,13 @@ class ExternalAgentBroker:
|
||||
approval_engine: ApprovalEngine,
|
||||
task_preparer: Callable[[Task], Coroutine[Any, Any, Task]] | None = None,
|
||||
communication: CommunicationManager | None = None,
|
||||
org_engine: Any | None = None,
|
||||
) -> None:
|
||||
self.store = store
|
||||
self.approval_engine = approval_engine
|
||||
self.task_preparer = task_preparer
|
||||
self.communication = communication
|
||||
self.org_engine = org_engine
|
||||
|
||||
@staticmethod
|
||||
def _normalize_external_agent_choice(value: Any) -> str:
|
||||
@@ -137,19 +139,19 @@ class ExternalAgentBroker:
|
||||
)
|
||||
return selected
|
||||
|
||||
async def _provider_stream_token_allows_resume(
|
||||
async def _stored_provider_token_allows_resume(
|
||||
self,
|
||||
*,
|
||||
adapter: ExternalAgentAdapter,
|
||||
task: Task,
|
||||
role_session_id: str,
|
||||
token: str,
|
||||
) -> bool:
|
||||
"""Cross-check an early stream token against its latest run status."""
|
||||
) -> bool | None:
|
||||
"""Return the latest durable resumability verdict for one provider token."""
|
||||
|
||||
list_sessions = getattr(self.store, "list_external_sessions", None)
|
||||
if not callable(list_sessions):
|
||||
return False
|
||||
return None
|
||||
project_id = str(task.project_id or "default").strip() or "default"
|
||||
kwargs: dict[str, Any] = {"project_id": project_id, "limit": 100}
|
||||
if role_session_id:
|
||||
@@ -160,19 +162,30 @@ class ExternalAgentBroker:
|
||||
sessions = await list_sessions(**kwargs)
|
||||
except Exception:
|
||||
logger.opt(exception=True).debug(
|
||||
"External resume restore: provider-stream status lookup failed"
|
||||
"External resume restore: provider-token status lookup failed"
|
||||
)
|
||||
return False
|
||||
for session in sessions:
|
||||
if str(getattr(session, "agent_type", "") or "").strip() != adapter.agent_type:
|
||||
continue
|
||||
if external_session_matches_provider_token(session, token):
|
||||
return (
|
||||
external_session_allows_resume(session)
|
||||
and str(getattr(session, "status", "") or "").strip().lower()
|
||||
in {"done", "suspended"}
|
||||
)
|
||||
return False
|
||||
return None
|
||||
matching = [
|
||||
session
|
||||
for session in sessions
|
||||
if str(getattr(session, "agent_type", "") or "").strip()
|
||||
== adapter.agent_type
|
||||
and external_session_matches_provider_token(session, token)
|
||||
]
|
||||
if not matching:
|
||||
return None
|
||||
selected, selected_token = select_best_external_resume_session(
|
||||
matching,
|
||||
agent_type=adapter.agent_type,
|
||||
project_id=project_id,
|
||||
)
|
||||
return bool(
|
||||
selected is not None
|
||||
and selected_token == token
|
||||
and external_session_allows_resume(selected)
|
||||
and str(getattr(selected, "status", "") or "").strip().lower()
|
||||
in {"done", "suspended"}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _task_explicitly_selected_external_agent(cls, task: Task, agent_type: str) -> bool:
|
||||
@@ -474,9 +487,12 @@ class ExternalAgentBroker:
|
||||
return
|
||||
if str(getattr(config, "session_mode", "") or "").strip().lower() == "new":
|
||||
return
|
||||
# Respect already-configured resume state.
|
||||
if str(getattr(config, "session_mode", "") or "").strip().lower() == "resume" and getattr(config, "session_id", ""):
|
||||
return
|
||||
configured_session_token = (
|
||||
str(getattr(config, "session_id", "") or "").strip()
|
||||
if str(getattr(config, "session_mode", "") or "").strip().lower()
|
||||
== "resume"
|
||||
else ""
|
||||
)
|
||||
|
||||
store = getattr(self, "store", None)
|
||||
if store is None or not hasattr(store, "get_external_session"):
|
||||
@@ -494,7 +510,7 @@ class ExternalAgentBroker:
|
||||
# Fix 5 PR6: canonical source is ``role_runtime_session.adapter_session_state[agent_type]``.
|
||||
# Check it first; the ExternalSession table is a compatibility
|
||||
# fallback for legacy rows written before PR6 landed.
|
||||
session_token = ""
|
||||
session_token = configured_session_token
|
||||
if role_session_id and hasattr(store, "get_role_session_adapter_state"):
|
||||
try:
|
||||
entry = await store.get_role_session_adapter_state(
|
||||
@@ -507,28 +523,29 @@ class ExternalAgentBroker:
|
||||
)
|
||||
entry = None
|
||||
if isinstance(entry, dict):
|
||||
session_token = str(
|
||||
role_session_token = str(
|
||||
entry.get("resume_session_id")
|
||||
or entry.get("provider_session_id")
|
||||
or ""
|
||||
).strip()
|
||||
if not is_provider_session_token(
|
||||
session_token,
|
||||
role_session_token,
|
||||
agent_type=adapter.agent_type,
|
||||
project_id=project_id,
|
||||
):
|
||||
session_token = ""
|
||||
if (
|
||||
session_token
|
||||
and str(entry.get("source", "") or "").strip()
|
||||
== "provider_stream"
|
||||
and not await self._provider_stream_token_allows_resume(
|
||||
role_session_token = ""
|
||||
session_token = session_token or role_session_token
|
||||
token_allows_resume = (
|
||||
await self._stored_provider_token_allows_resume(
|
||||
adapter=adapter,
|
||||
task=task,
|
||||
role_session_id=role_session_id,
|
||||
token=session_token,
|
||||
)
|
||||
):
|
||||
if session_token
|
||||
else None
|
||||
)
|
||||
if session_token and token_allows_resume is False:
|
||||
session_token = ""
|
||||
clear_role_state = getattr(
|
||||
store,
|
||||
@@ -544,8 +561,23 @@ class ExternalAgentBroker:
|
||||
)
|
||||
except Exception:
|
||||
logger.opt(exception=True).debug(
|
||||
"External resume restore: stale provider-stream token clear failed"
|
||||
"External resume restore: stale provider token clear failed"
|
||||
)
|
||||
if hasattr(config, "session_mode"):
|
||||
config.session_mode = "new"
|
||||
if hasattr(config, "session_id"):
|
||||
config.session_id = ""
|
||||
task.metadata = dict(task.metadata or {})
|
||||
task.metadata.pop("external_resume_session_id", None)
|
||||
task.metadata.pop("external_resume_session_scope_id", None)
|
||||
task.metadata.pop("external_resume_agent_type", None)
|
||||
task.metadata["external_resume_fallback"] = (
|
||||
"provider_terminal_failure"
|
||||
)
|
||||
if on_progress:
|
||||
await on_progress(
|
||||
f"[External resume] {adapter.agent_type} skipped prior failed session"
|
||||
)
|
||||
# Do not immediately rediscover the same unfinalized or
|
||||
# failed stream row through the compatibility fallback.
|
||||
return
|
||||
@@ -2552,20 +2584,34 @@ class ExternalAgentBroker:
|
||||
and hasattr(self.store, "get_role_session_adapter_state")
|
||||
and hasattr(self.store, "update_role_session_adapter_state")
|
||||
):
|
||||
# A stream token is durable early so Stop can retain it. A normal
|
||||
# terminal failure, however, must not leave that attempt's token
|
||||
# pinned for a later unrelated turn.
|
||||
# A stream token is durable early so Stop can retain it. A normal
|
||||
# terminal failure must clear both a token discovered by this task
|
||||
# and an older role token that this failed attempt resumed.
|
||||
try:
|
||||
current = await self.store.get_role_session_adapter_state(
|
||||
role_session_id,
|
||||
adapter.agent_type,
|
||||
)
|
||||
current_token = ""
|
||||
if isinstance(current, dict):
|
||||
current_token = str(
|
||||
current.get("resume_session_id")
|
||||
or current.get("provider_session_id")
|
||||
or ""
|
||||
).strip()
|
||||
failed_token = str(
|
||||
resume_session_id
|
||||
or provider_session_id
|
||||
or getattr(adapter.config, "session_id", "")
|
||||
or ""
|
||||
).strip()
|
||||
if (
|
||||
isinstance(current, dict)
|
||||
and str(current.get("last_task_id", "") or "").strip()
|
||||
== str(task.id or "").strip()
|
||||
and str(current.get("source", "") or "").strip()
|
||||
== "provider_stream"
|
||||
and (
|
||||
str(current.get("last_task_id", "") or "").strip()
|
||||
== str(task.id or "").strip()
|
||||
or bool(failed_token and current_token == failed_token)
|
||||
)
|
||||
):
|
||||
await self.store.update_role_session_adapter_state(
|
||||
role_session_id,
|
||||
@@ -2576,3 +2622,19 @@ class ExternalAgentBroker:
|
||||
logger.opt(exception=True).debug(
|
||||
"Failed to clear provider-stream role state after terminal failure"
|
||||
)
|
||||
if result.status != TaskStatus.DONE:
|
||||
failed_token = str(
|
||||
resume_session_id
|
||||
or provider_session_id
|
||||
or getattr(adapter.config, "session_id", "")
|
||||
or ""
|
||||
).strip()
|
||||
task_token = str(
|
||||
(task.metadata or {}).get("external_resume_session_id", "") or ""
|
||||
).strip()
|
||||
if failed_token and task_token == failed_token:
|
||||
task.metadata = dict(task.metadata or {})
|
||||
task.metadata.pop("external_resume_session_id", None)
|
||||
task.metadata.pop("external_resume_session_scope_id", None)
|
||||
task.metadata.pop("external_resume_agent_type", None)
|
||||
task.metadata["external_resume_fallback"] = "provider_terminal_failure"
|
||||
|
||||
@@ -61,6 +61,23 @@ def provider_token_from_external_session(
|
||||
) -> str:
|
||||
if not external_session_allows_resume(session):
|
||||
return ""
|
||||
return _provider_token_from_session_identity(
|
||||
session,
|
||||
agent_type=agent_type,
|
||||
project_id=project_id,
|
||||
)
|
||||
|
||||
|
||||
def _provider_token_from_session_identity(
|
||||
session: Any | None,
|
||||
*,
|
||||
agent_type: str,
|
||||
project_id: str,
|
||||
) -> str:
|
||||
"""Extract a provider token without treating its row as resumable."""
|
||||
|
||||
if session is None:
|
||||
return ""
|
||||
metadata = dict(getattr(session, "metadata", {}) or {})
|
||||
for candidate in (
|
||||
metadata.get("resume_session_id"),
|
||||
@@ -108,7 +125,7 @@ def select_best_external_resume_session(
|
||||
) -> tuple[Any | None, str]:
|
||||
"""Select the newest valid provider capability, ignoring placeholders."""
|
||||
|
||||
valid: list[tuple[Any, str]] = []
|
||||
latest_by_token: dict[str, Any] = {}
|
||||
normalized_agent = str(agent_type or "").strip()
|
||||
for session in list(sessions or []):
|
||||
if (
|
||||
@@ -116,23 +133,35 @@ def select_best_external_resume_session(
|
||||
!= normalized_agent
|
||||
):
|
||||
continue
|
||||
token = provider_token_from_external_session(
|
||||
token = _provider_token_from_session_identity(
|
||||
session,
|
||||
agent_type=normalized_agent,
|
||||
project_id=project_id,
|
||||
)
|
||||
if token:
|
||||
valid.append((session, token))
|
||||
if not token:
|
||||
continue
|
||||
current = latest_by_token.get(token)
|
||||
if current is None or _session_timestamp(session) > _session_timestamp(current):
|
||||
latest_by_token[token] = session
|
||||
|
||||
valid = [
|
||||
(session, token)
|
||||
for token, session in latest_by_token.items()
|
||||
if external_session_allows_resume(session)
|
||||
]
|
||||
if not valid:
|
||||
return None, ""
|
||||
|
||||
def _sort_key(item: tuple[Any, str]) -> tuple[float, str]:
|
||||
session, token = item
|
||||
updated_at = getattr(session, "updated_at", None)
|
||||
try:
|
||||
timestamp = float(updated_at.timestamp())
|
||||
except Exception:
|
||||
timestamp = 0.0
|
||||
return timestamp, token
|
||||
return _session_timestamp(session), token
|
||||
|
||||
return max(valid, key=_sort_key)
|
||||
|
||||
|
||||
def _session_timestamp(session: Any) -> float:
|
||||
updated_at = getattr(session, "updated_at", None)
|
||||
try:
|
||||
return float(updated_at.timestamp())
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
Reference in New Issue
Block a user