from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Literal from app.agents.registry import load_builtin_registry_indexes from app.agents.registry.models import CapabilityManifest, PermissionClass, SideEffectScope IsolationMode = Literal["none", "session", "worktree"] _WORKTREE_QUERY_MARKERS = ( "code", "repo", "repository", "git", "worktree", "branch", "patch", "diff", "refactor", "build", "test", "fix", "file", "files", "python", "typescript", "javascript", "代码", "仓库", "分支", "补丁", "重构", "构建", "测试", "修复", "文件", ) @dataclass(frozen=True) class IsolationDecision: mode: IsolationMode reason: str tool_names: tuple[str, ...] = () capability_ids: tuple[str, ...] = () metadata: dict[str, Any] = field(default_factory=dict) def _capability_metadata(capability: CapabilityManifest | None) -> dict[str, Any]: if capability is None: return {} return { "capability_id": capability.capability_id, "tool_name": capability.tool_name, "permission_class": capability.permission_class.value, "side_effect_scope": capability.side_effect_scope.value, "supports_retry": capability.supports_retry, "idempotent": capability.idempotent, "safe_for_parallel_use": capability.safe_for_parallel_use, "requires_confirmation": capability.requires_confirmation, } def select_isolation_strategy( *, user_query: str, tool_names: list[str] | tuple[str, ...], role_value: str, execution_mode: str | None, ) -> IsolationDecision: indexes = load_builtin_registry_indexes() capabilities: list[CapabilityManifest] = [] capability_ids: list[str] = [] for tool_name in tool_names: capability_id = indexes.capability_id_by_tool_name.get(tool_name) capability = indexes.capability_by_id.get(capability_id) if capability_id else None if capability is not None: capabilities.append(capability) capability_ids.append(capability.capability_id) normalized_query = (user_query or "").strip().lower() has_worktree_query_signal = any(marker in normalized_query for marker in _WORKTREE_QUERY_MARKERS) has_write_capability = any(cap.permission_class == PermissionClass.WRITE for cap in capabilities) has_external_capability = any(cap.permission_class == PermissionClass.EXTERNAL for cap in capabilities) has_non_parallel_capability = any(not cap.safe_for_parallel_use for cap in capabilities) has_stateful_side_effect = any( cap.side_effect_scope in {SideEffectScope.LOCAL_STATE, SideEffectScope.DB_WRITE} for cap in capabilities ) metadata = { "role": role_value, "execution_mode": execution_mode, "capabilities": [_capability_metadata(capability) for capability in capabilities], "workspace_strategy": "inline", "risk_level": "low", } if has_worktree_query_signal: return IsolationDecision( mode="worktree", reason="workspace_mutation_signals_detected", tool_names=tuple(tool_names), capability_ids=tuple(capability_ids), metadata={ **metadata, "workspace_strategy": "ephemeral_worktree", "risk_level": "high", }, ) if has_write_capability or has_stateful_side_effect or has_non_parallel_capability: return IsolationDecision( mode="session", reason="stateful_or_non_parallel_tooling", tool_names=tuple(tool_names), capability_ids=tuple(capability_ids), metadata={ **metadata, "workspace_strategy": "isolated_session", "risk_level": "medium", }, ) if execution_mode == "collaboration" or role_value in {"analyst", "librarian"} or has_external_capability: return IsolationDecision( mode="session", reason="context_heavy_or_external_retrieval", tool_names=tuple(tool_names), capability_ids=tuple(capability_ids), metadata={ **metadata, "workspace_strategy": "isolated_session", "risk_level": "medium", }, ) return IsolationDecision( mode="none", reason="inline_execution_is_sufficient", tool_names=tuple(tool_names), capability_ids=tuple(capability_ids), metadata=metadata, )