148 lines
4.6 KiB
Python
148 lines
4.6 KiB
Python
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,
|
|
)
|