feat(terminal): wire shared-container key into profile-scoped resolver and MEDIA delivery
Follow-up on @fangliquanflq's opt-in (#84775): after the profile-scoping fix (#94560) the container cache key is resolved in _resolve_container_task_id, so the shared key must unify profiles there too — 'shared:<key>' for every session of every opted-in profile AND for CLI/no-session runs. Delivery adds the shared sandbox layout as the first translation candidate. Empty key keeps strict per-profile isolation; SSH ignores the key entirely.
This commit is contained in:
@@ -1552,6 +1552,10 @@ def _docker_sandbox_dir_candidates(session_key: str = "") -> List[str]:
|
||||
from tools.environments.base import sanitize_task_id_for_path
|
||||
except Exception:
|
||||
return ["default"]
|
||||
# Explicit trusted-profiles opt-in: one shared container identity.
|
||||
shared = os.getenv("TERMINAL_DOCKER_SHARED_CONTAINER_KEY", "").strip()
|
||||
if shared:
|
||||
candidates.append(sanitize_task_id_for_path(f"shared:{shared}"))
|
||||
try:
|
||||
from hermes_cli.profiles import get_active_profile_name
|
||||
|
||||
|
||||
@@ -253,3 +253,59 @@ def test_ssh_backend_keeps_session_scoping(monkeypatch):
|
||||
assert terminal_tool._resolve_container_task_id(None) == "session:sess-A"
|
||||
finally:
|
||||
clear_session_vars(tokens)
|
||||
|
||||
|
||||
# --- Trusted-profiles shared container opt-in (#84671) ------------------------
|
||||
|
||||
|
||||
def test_shared_key_unifies_profiles(monkeypatch):
|
||||
from gateway.session_context import clear_session_vars, set_session_vars
|
||||
|
||||
_persistent_docker(monkeypatch)
|
||||
monkeypatch.setenv("TERMINAL_DOCKER_SHARED_CONTAINER_KEY", "team/workspace")
|
||||
tokens = set_session_vars(session_key="s1", profile="work")
|
||||
try:
|
||||
a = terminal_tool._resolve_container_task_id(None)
|
||||
finally:
|
||||
clear_session_vars(tokens)
|
||||
tokens = set_session_vars(session_key="s2", profile="research")
|
||||
try:
|
||||
b = terminal_tool._resolve_container_task_id(None)
|
||||
finally:
|
||||
clear_session_vars(tokens)
|
||||
assert a == b == "shared:team/workspace"
|
||||
|
||||
|
||||
def test_shared_key_applies_to_cli_no_session(monkeypatch):
|
||||
# CLI (no session key) must land in the same shared container as gateway
|
||||
# sessions, or the opt-in splits the container it exists to unify.
|
||||
_persistent_docker(monkeypatch)
|
||||
monkeypatch.setenv("TERMINAL_DOCKER_SHARED_CONTAINER_KEY", "team/workspace")
|
||||
monkeypatch.delenv("HERMES_SESSION_KEY", raising=False)
|
||||
assert terminal_tool._resolve_container_task_id(None) == "shared:team/workspace"
|
||||
|
||||
|
||||
def test_empty_shared_key_keeps_profile_scoping(monkeypatch):
|
||||
from gateway.session_context import clear_session_vars, set_session_vars
|
||||
|
||||
_persistent_docker(monkeypatch)
|
||||
monkeypatch.setenv("TERMINAL_DOCKER_SHARED_CONTAINER_KEY", "")
|
||||
tokens = set_session_vars(session_key="s1", profile="work")
|
||||
try:
|
||||
assert terminal_tool._resolve_container_task_id(None) == "profile:work"
|
||||
finally:
|
||||
clear_session_vars(tokens)
|
||||
|
||||
|
||||
def test_shared_key_ignored_outside_persistent_docker(monkeypatch):
|
||||
# The opt-in is a persistent-Docker concept only: SSH keeps session
|
||||
# scoping even when the key is set.
|
||||
from gateway.session_context import clear_session_vars, set_session_vars
|
||||
|
||||
monkeypatch.setenv("TERMINAL_ENV", "ssh")
|
||||
monkeypatch.setenv("TERMINAL_DOCKER_SHARED_CONTAINER_KEY", "team/workspace")
|
||||
tokens = set_session_vars(session_key="sess-A", profile="work")
|
||||
try:
|
||||
assert terminal_tool._resolve_container_task_id(None) == "session:sess-A"
|
||||
finally:
|
||||
clear_session_vars(tokens)
|
||||
|
||||
@@ -1513,11 +1513,25 @@ def _resolve_container_task_id(task_id: Optional[str]) -> str:
|
||||
# the default profile share the SAME container — CLI's historical key
|
||||
# IS the default profile's container.
|
||||
if _docker_persistent_profile_scoped():
|
||||
# Explicit opt-in: trusted profiles configuring the same
|
||||
# terminal.docker_shared_container_key share ONE container/cache
|
||||
# slot (and sandbox dir) regardless of profile name (#84671).
|
||||
shared = os.getenv("TERMINAL_DOCKER_SHARED_CONTAINER_KEY", "").strip()
|
||||
if shared:
|
||||
return f"shared:{shared}"
|
||||
profile = _current_session_profile() or "default"
|
||||
if profile == "default":
|
||||
return "default"
|
||||
return f"profile:{profile}"
|
||||
return f"session:{session_key}"
|
||||
# CLI/no-session path: honour the shared-container opt-in here too, or a
|
||||
# CLI run of a keyed profile would land in "default" while its gateway
|
||||
# sessions land in "shared:<key>" — splitting the very container the
|
||||
# setting exists to unify.
|
||||
if _docker_persistent_profile_scoped():
|
||||
shared = os.getenv("TERMINAL_DOCKER_SHARED_CONTAINER_KEY", "").strip()
|
||||
if shared:
|
||||
return f"shared:{shared}"
|
||||
return "default"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user