mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-16 02:46:44 +00:00
omx(team): auto-checkpoint worker-1 [1]
This commit is contained in:
@@ -23,6 +23,7 @@ class PortingModule:
|
||||
class PermissionDenial:
|
||||
tool_name: str
|
||||
reason: str
|
||||
status: str = 'blocked'
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -1,20 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from .path_scope import PathScopeDecision, WorkspacePathScope
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ToolPermissionContext:
|
||||
deny_names: frozenset[str] = field(default_factory=frozenset)
|
||||
deny_prefixes: tuple[str, ...] = ()
|
||||
workspace_scope: WorkspacePathScope | None = None
|
||||
cwd: Path | None = None
|
||||
|
||||
@classmethod
|
||||
def from_iterables(cls, deny_names: list[str] | None = None, deny_prefixes: list[str] | None = None) -> 'ToolPermissionContext':
|
||||
def from_iterables(
|
||||
cls,
|
||||
deny_names: list[str] | None = None,
|
||||
deny_prefixes: list[str] | None = None,
|
||||
workspace_root: str | Path | None = None,
|
||||
workspace_roots: list[str | Path] | tuple[str | Path, ...] | None = None,
|
||||
cwd: str | Path | None = None,
|
||||
) -> 'ToolPermissionContext':
|
||||
roots: list[str | Path] = []
|
||||
if workspace_roots:
|
||||
roots.extend(workspace_roots)
|
||||
if workspace_root is not None:
|
||||
roots.append(workspace_root)
|
||||
return cls(
|
||||
deny_names=frozenset(name.lower() for name in (deny_names or [])),
|
||||
deny_prefixes=tuple(prefix.lower() for prefix in (deny_prefixes or [])),
|
||||
workspace_scope=WorkspacePathScope.from_roots(roots) if roots else None,
|
||||
cwd=Path(cwd).expanduser().resolve(strict=False) if cwd is not None else None,
|
||||
)
|
||||
|
||||
def blocks(self, tool_name: str) -> bool:
|
||||
lowered = tool_name.lower()
|
||||
return lowered in self.deny_names or any(lowered.startswith(prefix) for prefix in self.deny_prefixes)
|
||||
|
||||
def validate_payload_scope(self, tool_name: str, payload: str) -> PathScopeDecision:
|
||||
if self.workspace_scope is None or not _scope_checked_tool(tool_name):
|
||||
return PathScopeDecision(True, 'workspace path scope not required for this tool')
|
||||
return self.workspace_scope.validate_payload(payload, cwd=self.cwd)
|
||||
|
||||
|
||||
def _scope_checked_tool(tool_name: str) -> bool:
|
||||
lowered = tool_name.lower()
|
||||
return any(marker in lowered for marker in ('bash', 'shell', 'powershell', 'fileread', 'filewrite', 'fileedit'))
|
||||
|
||||
@@ -82,6 +82,7 @@ class QueryEnginePort:
|
||||
f'Matched commands: {", ".join(matched_commands) if matched_commands else "none"}',
|
||||
f'Matched tools: {", ".join(matched_tools) if matched_tools else "none"}',
|
||||
f'Permission denials: {len(denied_tools)}',
|
||||
*(f'Permission denial: {denial.tool_name} status={denial.status} reason={denial.reason}' for denial in denied_tools),
|
||||
]
|
||||
output = self._format_output(summary_lines)
|
||||
projected_usage = self.total_usage.add_turn(prompt, output)
|
||||
@@ -116,7 +117,13 @@ class QueryEnginePort:
|
||||
if matched_tools:
|
||||
yield {'type': 'tool_match', 'tools': matched_tools}
|
||||
if denied_tools:
|
||||
yield {'type': 'permission_denial', 'denials': [denial.tool_name for denial in denied_tools]}
|
||||
yield {
|
||||
'type': 'permission_denial',
|
||||
'denials': [
|
||||
{'tool_name': denial.tool_name, 'reason': denial.reason, 'status': denial.status}
|
||||
for denial in denied_tools
|
||||
],
|
||||
}
|
||||
result = self.submit_message(prompt, matched_commands, matched_tools, denied_tools)
|
||||
yield {'type': 'message_delta', 'text': result.output}
|
||||
yield {
|
||||
|
||||
17
src/tools.py
17
src/tools.py
@@ -78,10 +78,25 @@ def find_tools(query: str, limit: int = 20) -> list[PortingModule]:
|
||||
return matches[:limit]
|
||||
|
||||
|
||||
def execute_tool(name: str, payload: str = '') -> ToolExecution:
|
||||
def execute_tool(name: str, payload: str = '', permission_context: ToolPermissionContext | None = None) -> ToolExecution:
|
||||
module = get_tool(name)
|
||||
if module is None:
|
||||
return ToolExecution(name=name, source_hint='', payload=payload, handled=False, message=f'Unknown mirrored tool: {name}')
|
||||
if permission_context and permission_context.blocks(module.name):
|
||||
return ToolExecution(name=module.name, source_hint=module.source_hint, payload=payload, handled=False, message=f"Permission denied for mirrored tool '{module.name}'.")
|
||||
if permission_context:
|
||||
scope_decision = permission_context.validate_payload_scope(module.name, payload)
|
||||
if not scope_decision.allowed:
|
||||
return ToolExecution(
|
||||
name=module.name,
|
||||
source_hint=module.source_hint,
|
||||
payload=payload,
|
||||
handled=False,
|
||||
message=(
|
||||
f"Permission denied for mirrored tool '{module.name}': {scope_decision.reason}"
|
||||
f" (candidate={scope_decision.candidate!r}, resolved={scope_decision.resolved!r})."
|
||||
),
|
||||
)
|
||||
action = f"Mirrored tool '{module.name}' from {module.source_hint} would handle payload {payload!r}."
|
||||
return ToolExecution(name=module.name, source_hint=module.source_hint, payload=payload, handled=True, message=action)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user