refactor(isolation): simplify agent session isolation and remove legacy home-isolation helpers

This commit is contained in:
2026-07-23 22:05:29 +09:00
parent 15ffc8f6bb
commit f0a2103edf
7 changed files with 91 additions and 347 deletions
+33 -158
View File
@@ -546,7 +546,11 @@ for s in d.get('herdr_sessions', []):
if s.get('name') == name:
print(s.get('herdr_workspace') or s.get('herdr_server') or 'default')
sys.exit(0)
print(os.environ.get('HERDR_SERVER_NAME', 'default'))
fallback = os.environ.get('HERDR_SERVER_NAME', '')
if not fallback or fallback == 'default':
pwd = os.path.abspath(os.getcwd())
fallback = os.path.basename(pwd)
print(fallback or 'default')
"
}
@@ -890,13 +894,12 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
row = row or {}
epoch = row.get("herdr_session_epoch", 0)
cwd = row.get("pane", {}).get("cwd", "") or ws
iso = row.get("isolation", {}).get("root") if isinstance(row.get("isolation"), dict) else None
if workspace_key(cwd) != workspace_key(ws):
return False
if agent == "claude":
base = f"{iso}/projects" if iso else c_dir
base = c_dir
key = workspace_key(ws)
path = f"{base}/{key}/{uuid}.jsonl"
if not os.path.exists(path):
@@ -917,21 +920,24 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
return False
elif agent == "agy":
base = f"{iso}/.gemini/antigravity-cli/conversations" if iso else f"{home}/.gemini/antigravity-cli/conversations"
base = f"{home}/.gemini/antigravity-cli/conversations"
path = f"{base}/{uuid}.db"
if not os.path.exists(path):
return False
if epoch and os.path.getmtime(path) < epoch:
return False
if mode == "discover":
lc = f"{iso}/.gemini/antigravity-cli/cache/last_conversations.json" if iso else f"{home}/.gemini/antigravity-cli/cache/last_conversations.json"
lc = f"{home}/.gemini/antigravity-cli/cache/last_conversations.json"
cache_match = False
if os.path.exists(lc):
try:
with open(lc) as f:
lc_data = json.load(f)
if lc_data.get(cwd) != uuid:
return False
cache_match = (lc_data.get(cwd) == uuid)
except Exception:
cache_match = False
if not cache_match:
if uuid in (row.get("_sibling_claimed_uuids") or []):
return False
try:
conn = sqlite3.connect(path)
@@ -943,7 +949,7 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
return False
elif agent == "hermes":
hdb = f"{iso}/.hermes/state.db" if iso else f"{home}/.hermes/state.db"
hdb = f"{home}/.hermes/state.db"
if not os.path.exists(hdb):
return False
if epoch and os.path.getmtime(hdb) < epoch:
@@ -958,7 +964,7 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
return False
elif agent == "cline":
base = f"{iso}/sessions" if iso else f"{home}/.cline/data/sessions"
base = f"{home}/.cline/data/sessions"
path = f"{base}/{uuid}/{uuid}.json"
if not os.path.exists(path):
return False
@@ -1265,169 +1271,34 @@ capture_conversation_id() {
}
# ---------------------------------------------------------------------------
# Session isolation (all-L2) — implementation_plan.session_isolation.md Rev.3
# Session isolation — Universal Global Config (Option A, Job 536a6625)
#
# Every isolated session gets its own state home `.mam/agent_homes/<uuid>/`;
# auth/config files are SYMLINKED into it (never copied — token refresh must
# converge on the real files). Levers per agent (Phase 0 measured matrix):
# claude: CLAUDE_CONFIG_DIR=<root> conv: <root>/projects/<key>/<uuid>.jsonl
# cline: --data-dir <root> conv: <root>/sessions/<id>/<id>.json
# agy: HOME=<root> conv: <root>/.gemini/antigravity-cli/conversations/
# hermes: HOME=<root> conv: <root>/.hermes/state.db
# Config-home isolation (.mam/agent_homes/<uuid>/) was removed in favor of:
# 1. Universal Global Config: all agents read/write standard ~/.claude, ~/.gemini,
# ~/.hermes, ~/.cline user configuration and credential stores.
# 2. Process Isolation: each agent-workspace pair runs in its own herdr pane.
# 3. Conversation Isolation: session UUIDs discriminate conversation history.
#
# provision_isolation <agent> <root> — mkdir + seed; prints comma-joined seeded list
# isolation_lever <agent> — claude_config_dir|cline_data_dir|home
# isolation_env_prefix <agent> <root> — "VAR=<root> " spawn prefix ('' for cline)
# isolation_cmd_args <agent> <root> — extra spawn CLI args ('' unless cline)
# Stubbed isolation functions kept for backward compatibility:
# ---------------------------------------------------------------------------
provision_isolation() {
local agent="$1" root="$2" seeded="" f base
mkdir -p "$root"
case "$agent" in
claude)
if [ -e "$HOME/.claude.json" ]; then ln -sfn "$HOME/.claude.json" "$root/.claude.json"; seeded=".claude.json"; fi
if [ -e "$HOME/.claude/.credentials.json" ]; then ln -sfn "$HOME/.claude/.credentials.json" "$root/.credentials.json"; seeded="${seeded:+$seeded,}.credentials.json"; fi
if [ -e "$HOME/.claude/settings.json" ]; then
if [ ! -e "$root/settings.json" ]; then cp -a "$HOME/.claude/settings.json" "$root/settings.json"; fi
seeded="${seeded:+$seeded,}settings.json"
fi
if [ -d "$HOME/.claude/plugins" ]; then ln -sfn "$HOME/.claude/plugins" "$root/plugins"; seeded="${seeded:+$seeded,}plugins"; fi
if [ -d "$HOME/.claude/session-env" ]; then ln -sfn "$HOME/.claude/session-env" "$root/session-env"; seeded="${seeded:+$seeded,}session-env"; fi
if [ -d "$HOME/.claude/sessions" ]; then ln -sfn "$HOME/.claude/sessions" "$root/sessions"; seeded="${seeded:+$seeded,}sessions"; fi
if [ -d "$HOME/.claude/cache" ]; then ln -sfn "$HOME/.claude/cache" "$root/cache"; seeded="${seeded:+$seeded,}cache"; fi
;;
cline)
# CLI 가 --data-dir <root> 를 읽을 때 최상위 루트 하위에서 설정을 찾으므로 다이렉트 맵핑
mkdir -p "$root/settings"
for f in "$HOME/.cline/data/settings/"*; do
[ -e "$f" ] || continue
base="$(basename "$f")"
ln -sfn "$f" "$root/settings/$base"; seeded="${seeded:+$seeded,}settings/$base"
done
if [ -e "$HOME/.cline/data/globalState.json" ]; then
ln -sfn "$HOME/.cline/data/globalState.json" "$root/globalState.json"; seeded="${seeded:+$seeded,}globalState.json"
fi
# 인증과 DB 바인딩을 연계하여 Welcome Screen 튕김 방지
mkdir -p "$root/db"
for f in "$HOME/.cline/data/db/"*; do
[ -e "$f" ] || continue
base="$(basename "$f")"
# DB 락 경쟁 크래시 방지를 위해 물리 복사(cp -p)로 직접 주입
cp -p "$f" "$root/db/$base"
seeded="${seeded:+$seeded,}db/$base"
done
;;
agy)
mkdir -p "$root/.gemini/antigravity-cli"
for f in oauth_creds.json google_accounts.json installation_id settings.json state.json; do
if [ -e "$HOME/.gemini/$f" ]; then ln -sfn "$HOME/.gemini/$f" "$root/.gemini/$f"; seeded="${seeded:+$seeded,}.gemini/$f"; fi
done
for f in antigravity-oauth-token installation_id settings.json conversation_summaries.db jetski_state.pbtxt; do
if [ -e "$HOME/.gemini/antigravity-cli/$f" ]; then ln -sfn "$HOME/.gemini/antigravity-cli/$f" "$root/.gemini/antigravity-cli/$f"; seeded="${seeded:+$seeded,}.gemini/antigravity-cli/$f"; fi
done
if [ -d "$HOME/.gemini/antigravity" ]; then
ln -sfn "$HOME/.gemini/antigravity" "$root/.gemini/antigravity"
seeded="${seeded:+$seeded,}.gemini/antigravity"
fi
if [ -d "$HOME/.gemini/antigravity-ide" ]; then
if [ ! -d "$root/.gemini/antigravity-ide" ]; then cp -a "$HOME/.gemini/antigravity-ide" "$root/.gemini/antigravity-ide"; fi
seeded="${seeded:+$seeded,}.gemini/antigravity-ide"
fi
if [ -d "$HOME/.gemini/config" ]; then
ln -sfn "$HOME/.gemini/config" "$root/.gemini/config"
seeded="${seeded:+$seeded,}.gemini/config"
fi
# On macOS, seed ~/Library/Keychains to allow isolated agy to query Keychain Access credentials
# Also seed Preferences and Application Support for Antigravity settings/TOS/Theme (using cp -a for write isolation)
if [ "$(uname)" = "Darwin" ]; then
mkdir -p "$root/Library"
if [ -d "$HOME/Library/Keychains" ]; then
ln -sfn "$HOME/Library/Keychains" "$root/Library/Keychains"
seeded="${seeded:+$seeded,}Library/Keychains"
fi
mkdir -p "$root/Library/Preferences"
for plist in com.google.antigravity.plist com.google.antigravity-ide.plist com.google.GeminiMacOS.plist com.google.GeminiMacOS.shareddata.plist com.google.GeminiMacOS.launcher.plist; do
if [ -f "$HOME/Library/Preferences/$plist" ]; then
if [ ! -f "$root/Library/Preferences/$plist" ]; then cp -a "$HOME/Library/Preferences/$plist" "$root/Library/Preferences/$plist"; fi
seeded="${seeded:+$seeded,}Library/Preferences/$plist"
fi
done
mkdir -p "$root/Library/Application Support"
if [ -d "$HOME/Library/Application Support/Antigravity" ]; then
if [ ! -d "$root/Library/Application Support/Antigravity" ]; then cp -a "$HOME/Library/Application Support/Antigravity" "$root/Library/Application Support/Antigravity"; fi
seeded="${seeded:+$seeded,}Library/Application Support/Antigravity"
fi
if [ -d "$HOME/Library/Application Support/Antigravity IDE" ]; then
if [ ! -d "$root/Library/Application Support/Antigravity IDE" ]; then cp -a "$HOME/Library/Application Support/Antigravity IDE" "$root/Library/Application Support/Antigravity IDE"; fi
seeded="${seeded:+$seeded,}Library/Application Support/Antigravity IDE"
fi
if [ -d "$HOME/Library/Application Support/com.google.GeminiMacOS" ]; then
if [ ! -d "$root/Library/Application Support/com.google.GeminiMacOS" ]; then cp -a "$HOME/Library/Application Support/com.google.GeminiMacOS" "$root/Library/Application Support/com.google.GeminiMacOS"; fi
seeded="${seeded:+$seeded,}Library/Application Support/com.google.GeminiMacOS"
fi
if [ -d "$HOME/Library/Group Containers/group.com.google.gemini" ]; then
mkdir -p "$root/Library/Group Containers"
ln -sfn "$HOME/Library/Group Containers/group.com.google.gemini" "$root/Library/Group Containers/group.com.google.gemini"
seeded="${seeded:+$seeded,}Library/Group Containers/group.com.google.gemini"
fi
else
# On Linux/Unix, seed XDG config and local data dirs for Antigravity settings/TOS (using cp -a for write isolation)
local xdg_config="${XDG_CONFIG_HOME:-$HOME/.config}"
local xdg_data="${XDG_DATA_HOME:-$HOME/.local/share}"
if [ -d "$xdg_config/Antigravity" ]; then
mkdir -p "$root/.config"
if [ ! -d "$root/.config/Antigravity" ]; then cp -a "$xdg_config/Antigravity" "$root/.config/Antigravity"; fi
seeded="${seeded:+$seeded,}.config/Antigravity"
elif [ -d "$xdg_config/antigravity" ]; then
mkdir -p "$root/.config"
if [ ! -d "$root/.config/antigravity" ]; then cp -a "$xdg_config/antigravity" "$root/.config/antigravity"; fi
seeded="${seeded:+$seeded,}.config/antigravity"
fi
if [ -d "$xdg_data/Antigravity" ]; then
mkdir -p "$root/.local/share"
if [ ! -d "$root/.local/share/Antigravity" ]; then cp -a "$xdg_data/Antigravity" "$root/.local/share/Antigravity"; fi
seeded="${seeded:+$seeded,}.local/share/Antigravity"
elif [ -d "$xdg_data/antigravity" ]; then
mkdir -p "$root/.local/share"
if [ ! -d "$root/.local/share/antigravity" ]; then cp -a "$xdg_data/antigravity" "$root/.local/share/antigravity"; fi
seeded="${seeded:+$seeded,}.local/share/antigravity"
fi
fi
;;
hermes)
mkdir -p "$root/.hermes"
for f in auth.json config.yaml .env; do
if [ -e "$HOME/.hermes/$f" ]; then ln -sfn "$HOME/.hermes/$f" "$root/.hermes/$f"; seeded="${seeded:+$seeded,}.hermes/$f"; fi
done
;;
esac
printf '%s\n' "$seeded"
local agent="$1" root="$2"
printf ''
}
isolation_lever() {
case "$1" in
claude) echo "claude_config_dir" ;;
cline) echo "cline_data_dir" ;;
agy|hermes) echo "home" ;;
*) echo "" ;;
claude|agy|hermes|cline) echo "none" ;;
*) echo "" ;;
esac
}
isolation_env_prefix() {
local agent="$1" root="$2"
case "$agent" in
claude) printf 'CLAUDE_CONFIG_DIR=%q ' "$root" ;;
agy|hermes) printf 'HOME=%q ' "$root" ;;
*) : ;;
esac
:
}
isolation_cmd_args() {
local agent="$1" root="$2"
case "$agent" in
cline) printf -- '--data-dir %q' "$root" ;;
*) : ;;
esac
:
}
# ---------------------------------------------------------------------------
@@ -1596,11 +1467,15 @@ wait_for_tui_ready() {
for i in {1..30}; do
if _pane_dialog_open "$sess"; then
if printf '%s\n' "$(_pane_tail "$sess" 5)" | grep -q 'Press Enter to continue'; then
_sks_herdr send-keys -t "$sess" Enter || true
sleep 1
fi
sleep 1
continue
fi
local content
content=$($local_herdr capture-pane -p -t "$sess" 2>/dev/null || echo "")
content=$(_sks_herdr capture-pane -p -t "$sess" 2>/dev/null || echo "")
if [ -n "$content" ]; then
case "$agent" in
claude)