refactor(isolation): simplify agent session isolation and remove legacy home-isolation helpers
This commit is contained in:
+33
-158
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user