patchpal 0.22.1__tar.gz → 0.22.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {patchpal-0.22.1/patchpal.egg-info → patchpal-0.22.3}/PKG-INFO +2 -2
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/__init__.py +1 -1
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/agent/function_calling.py +19 -1
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/agent/react.py +8 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/cli/autopilot.py +36 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/cli/interactive.py +40 -7
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/cli/sandbox.py +0 -3
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/context.py +23 -1
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/permissions.py +57 -6
- patchpal-0.22.3/patchpal/tools/audit.py +405 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/code_analysis.py +0 -1
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/common.py +32 -3
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/file_reading.py +57 -17
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/file_writing.py +0 -8
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/find_tool.py +0 -5
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/grep_tool.py +0 -3
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/repo_map.py +19 -11
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/shell_tools.py +45 -12
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/todo_tools.py +0 -9
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/user_interaction.py +18 -5
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/web_tools.py +31 -4
- {patchpal-0.22.1 → patchpal-0.22.3/patchpal.egg-info}/PKG-INFO +2 -2
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal.egg-info/SOURCES.txt +1 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal.egg-info/requires.txt +1 -1
- {patchpal-0.22.1 → patchpal-0.22.3}/pyproject.toml +1 -1
- {patchpal-0.22.1 → patchpal-0.22.3}/LICENSE +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/MANIFEST.in +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/README.md +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/agent/__init__.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/cli/__init__.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/cli/mcp.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/cli/streaming.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/config.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/prompts/react_prompt.md +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/prompts/system_prompt.md +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/skills.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/__init__.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/definitions.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/image_handler.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/mcp.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal/tools/tool_schema.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal.egg-info/dependency_links.txt +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal.egg-info/entry_points.txt +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/patchpal.egg-info/top_level.txt +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/setup.cfg +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_agent.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_cli.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_config_dynamic.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_context.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_custom_tools.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_enabled_tools.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_find_tool.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_guardrails.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_image_blocking.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_maximum_security.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_mcp_config.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_memory.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_operational_safety.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_optional_tools.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_permissions.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_react.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_reasoning_content.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_repo_map.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_simplified_prompt.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_skills.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_streaming.py +0 -0
- {patchpal-0.22.1 → patchpal-0.22.3}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchpal
|
|
3
|
-
Version: 0.22.
|
|
3
|
+
Version: 0.22.3
|
|
4
4
|
Summary: An agentic coding and automation assistant, supporting both local and cloud LLMs
|
|
5
5
|
Author: PatchPal Contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -30,7 +30,7 @@ Requires-Dist: boto3
|
|
|
30
30
|
Requires-Dist: pymupdf>=1.23.0
|
|
31
31
|
Requires-Dist: python-docx>=1.0.0
|
|
32
32
|
Requires-Dist: python-pptx>=0.6.0
|
|
33
|
-
Requires-Dist: tree-sitter-language-pack
|
|
33
|
+
Requires-Dist: tree-sitter-language-pack<1.0.0,>=0.3.0
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
36
36
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
@@ -479,6 +479,10 @@ class PatchPalAgent:
|
|
|
479
479
|
self.cumulative_input_tokens = 0
|
|
480
480
|
self.cumulative_output_tokens = 0
|
|
481
481
|
|
|
482
|
+
# Track last prompt tokens from most recent API response (includes cache operations)
|
|
483
|
+
# This is the ACTUAL token count sent to the API, used for accurate context management
|
|
484
|
+
self.last_prompt_tokens = None
|
|
485
|
+
|
|
482
486
|
# Track cache-related tokens (for Anthropic/Bedrock models with prompt caching)
|
|
483
487
|
self.cumulative_cache_creation_tokens = 0
|
|
484
488
|
self.cumulative_cache_read_tokens = 0
|
|
@@ -508,6 +512,14 @@ class PatchPalAgent:
|
|
|
508
512
|
# Load MEMORY.md if it exists and has non-template content
|
|
509
513
|
self._load_project_memory()
|
|
510
514
|
|
|
515
|
+
# Log session start
|
|
516
|
+
try:
|
|
517
|
+
from patchpal.tools.audit import log_session_start
|
|
518
|
+
|
|
519
|
+
log_session_start(agent_type="function_calling", model=self.model_id)
|
|
520
|
+
except Exception:
|
|
521
|
+
pass # Don't fail if audit logging fails
|
|
522
|
+
|
|
511
523
|
def _load_project_memory(self):
|
|
512
524
|
"""Load MEMORY.md file at session start if it has non-template content."""
|
|
513
525
|
try:
|
|
@@ -945,7 +957,10 @@ It's currently empty (just the template). The file is automatically loaded at se
|
|
|
945
957
|
|
|
946
958
|
# Check for compaction BEFORE starting work
|
|
947
959
|
# This ensures we never compact mid-execution and lose tool results
|
|
948
|
-
|
|
960
|
+
# Use last_prompt_tokens from previous API call for accurate check (includes cache operations)
|
|
961
|
+
if self.enable_auto_compact and self.context_manager.needs_compaction(
|
|
962
|
+
self.messages, actual_prompt_tokens=self.last_prompt_tokens
|
|
963
|
+
):
|
|
949
964
|
self._perform_auto_compaction()
|
|
950
965
|
|
|
951
966
|
# Agent loop with interrupt handling
|
|
@@ -1092,7 +1107,10 @@ It's currently empty (just the template). The file is automatically loaded at se
|
|
|
1092
1107
|
last_prompt_tokens = None # Track for reactive context management
|
|
1093
1108
|
if hasattr(response, "usage") and response.usage:
|
|
1094
1109
|
if hasattr(response.usage, "prompt_tokens"):
|
|
1110
|
+
# LiteLLM already includes cache operations in prompt_tokens for Anthropic/Bedrock
|
|
1111
|
+
# (see litellm/llms/anthropic/chat/transformation.py)
|
|
1095
1112
|
last_prompt_tokens = response.usage.prompt_tokens
|
|
1113
|
+
self.last_prompt_tokens = last_prompt_tokens # Store for /status command
|
|
1096
1114
|
self.cumulative_input_tokens += response.usage.prompt_tokens
|
|
1097
1115
|
if hasattr(response.usage, "completion_tokens"):
|
|
1098
1116
|
self.cumulative_output_tokens += response.usage.completion_tokens
|
|
@@ -164,6 +164,14 @@ class ReActAgent:
|
|
|
164
164
|
# Load project memory
|
|
165
165
|
self._load_project_memory()
|
|
166
166
|
|
|
167
|
+
# Log session start
|
|
168
|
+
try:
|
|
169
|
+
from patchpal.tools.audit import log_session_start
|
|
170
|
+
|
|
171
|
+
log_session_start(agent_type="react", model=self.model_id)
|
|
172
|
+
except Exception:
|
|
173
|
+
pass # Don't fail if audit logging fails
|
|
174
|
+
|
|
167
175
|
def _load_project_memory(self):
|
|
168
176
|
"""Load project memory file if it exists."""
|
|
169
177
|
from pathlib import Path
|
|
@@ -112,10 +112,27 @@ def autopilot_loop(
|
|
|
112
112
|
print(f"🔄 Autopilot Iteration {iteration + 1}/{max_iterations}")
|
|
113
113
|
print(f"{'=' * 80}\n")
|
|
114
114
|
|
|
115
|
+
# Log user prompt to audit log (first iteration only)
|
|
116
|
+
if iteration == 0:
|
|
117
|
+
try:
|
|
118
|
+
from patchpal.tools.audit import log_user_prompt
|
|
119
|
+
|
|
120
|
+
log_user_prompt(prompt)
|
|
121
|
+
except Exception:
|
|
122
|
+
pass # Don't fail if audit logging fails
|
|
123
|
+
|
|
115
124
|
# Run agent with the SAME prompt every time
|
|
116
125
|
# The agent's conversation history accumulates, so it can see all previous work
|
|
117
126
|
response = agent.run(prompt, max_iterations=100)
|
|
118
127
|
|
|
128
|
+
# Log agent response to audit log
|
|
129
|
+
try:
|
|
130
|
+
from patchpal.tools.audit import log_agent_response
|
|
131
|
+
|
|
132
|
+
log_agent_response(response, success=True)
|
|
133
|
+
except Exception:
|
|
134
|
+
pass # Don't fail if audit logging fails
|
|
135
|
+
|
|
119
136
|
print(f"\n{'=' * 80}")
|
|
120
137
|
print("📝 Agent Response:")
|
|
121
138
|
print(f"{'=' * 80}")
|
|
@@ -144,6 +161,16 @@ def autopilot_loop(
|
|
|
144
161
|
)
|
|
145
162
|
if agent.cumulative_cost > 0:
|
|
146
163
|
print(f"Total cost: ${agent.cumulative_cost:.4f}")
|
|
164
|
+
|
|
165
|
+
# Log successful session end
|
|
166
|
+
try:
|
|
167
|
+
from patchpal.tools.audit import log_session_end
|
|
168
|
+
from patchpal.tools.common import get_operation_count
|
|
169
|
+
|
|
170
|
+
log_session_end(total_operations=get_operation_count(), success=True)
|
|
171
|
+
except Exception:
|
|
172
|
+
pass # Don't fail if audit logging fails
|
|
173
|
+
|
|
147
174
|
return response
|
|
148
175
|
|
|
149
176
|
# Stop hook: Agent tried to complete, but no completion promise
|
|
@@ -168,6 +195,15 @@ def autopilot_loop(
|
|
|
168
195
|
if agent.cumulative_cost > 0:
|
|
169
196
|
print(f"Total cost: ${agent.cumulative_cost:.4f}")
|
|
170
197
|
|
|
198
|
+
# Log session end
|
|
199
|
+
try:
|
|
200
|
+
from patchpal.tools.audit import log_session_end
|
|
201
|
+
from patchpal.tools.common import get_operation_count
|
|
202
|
+
|
|
203
|
+
log_session_end(total_operations=get_operation_count(), success=False)
|
|
204
|
+
except Exception:
|
|
205
|
+
pass # Don't fail if audit logging fails
|
|
206
|
+
|
|
171
207
|
return None
|
|
172
208
|
|
|
173
209
|
|
|
@@ -564,6 +564,15 @@ Supported models: Any LiteLLM-supported model
|
|
|
564
564
|
|
|
565
565
|
audit_logger.info(", ".join(log_parts))
|
|
566
566
|
|
|
567
|
+
# Log structured session end
|
|
568
|
+
try:
|
|
569
|
+
from patchpal.tools.audit import log_session_end
|
|
570
|
+
from patchpal.tools.common import get_operation_count
|
|
571
|
+
|
|
572
|
+
log_session_end(total_operations=get_operation_count(), success=True)
|
|
573
|
+
except Exception:
|
|
574
|
+
pass # Don't fail if audit logging fails
|
|
575
|
+
|
|
567
576
|
print("\nGoodbye!")
|
|
568
577
|
break
|
|
569
578
|
|
|
@@ -661,7 +670,9 @@ Supported models: Any LiteLLM-supported model
|
|
|
661
670
|
|
|
662
671
|
# Handle /status command - show context window usage
|
|
663
672
|
if user_input.lower() in ["status", "/status"]:
|
|
664
|
-
stats = agent.context_manager.get_usage_stats(
|
|
673
|
+
stats = agent.context_manager.get_usage_stats(
|
|
674
|
+
agent.messages, actual_prompt_tokens=agent.last_prompt_tokens
|
|
675
|
+
)
|
|
665
676
|
|
|
666
677
|
print("\n" + "=" * 70)
|
|
667
678
|
print("\033[1;36mContext Window Status\033[0m")
|
|
@@ -1542,10 +1553,17 @@ Supported models: Any LiteLLM-supported model
|
|
|
1542
1553
|
if skill_args:
|
|
1543
1554
|
prompt += f"\n\nArguments: {skill_args}"
|
|
1544
1555
|
|
|
1545
|
-
# Log
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1556
|
+
# Log skill invocation to audit log with hash-chaining
|
|
1557
|
+
try:
|
|
1558
|
+
from patchpal.tools.audit import log_user_prompt
|
|
1559
|
+
|
|
1560
|
+
log_user_prompt(f"/{skill_name} {skill_args}")
|
|
1561
|
+
except Exception:
|
|
1562
|
+
# Fallback to old-style logging if audit fails
|
|
1563
|
+
audit_logger.info(
|
|
1564
|
+
_sanitize_for_logging(f"USER_PROMPT: /{skill_name} {skill_args}")
|
|
1565
|
+
)
|
|
1566
|
+
|
|
1549
1567
|
result = agent.run(prompt, max_iterations=max_iterations)
|
|
1550
1568
|
|
|
1551
1569
|
print("\n" + "=" * 80)
|
|
@@ -1565,10 +1583,25 @@ Supported models: Any LiteLLM-supported model
|
|
|
1565
1583
|
# Run the agent (Ctrl-C here will interrupt agent, not exit)
|
|
1566
1584
|
try:
|
|
1567
1585
|
print() # Add blank line before agent output
|
|
1568
|
-
# Log user prompt to audit log
|
|
1569
|
-
|
|
1586
|
+
# Log user prompt to audit log with hash-chaining
|
|
1587
|
+
try:
|
|
1588
|
+
from patchpal.tools.audit import log_user_prompt
|
|
1589
|
+
|
|
1590
|
+
log_user_prompt(user_input)
|
|
1591
|
+
except Exception:
|
|
1592
|
+
# Fallback to old-style logging if audit fails
|
|
1593
|
+
audit_logger.info(_sanitize_for_logging(f"USER_PROMPT: {user_input}"))
|
|
1594
|
+
|
|
1570
1595
|
result = agent.run(user_input, max_iterations=max_iterations)
|
|
1571
1596
|
|
|
1597
|
+
# Log agent response to audit log with hash-chaining
|
|
1598
|
+
try:
|
|
1599
|
+
from patchpal.tools.audit import log_agent_response
|
|
1600
|
+
|
|
1601
|
+
log_agent_response(result, success=True)
|
|
1602
|
+
except Exception:
|
|
1603
|
+
pass # Don't fail if audit logging fails
|
|
1604
|
+
|
|
1572
1605
|
print("\n" + "=" * 80)
|
|
1573
1606
|
print("\033[1;32mAgent:\033[0m")
|
|
1574
1607
|
print("=" * 80)
|
|
@@ -686,9 +686,6 @@ DESCRIPTION:
|
|
|
686
686
|
- Pre-built image with patchpal installed (fast startup)
|
|
687
687
|
- Auto-mounts ~/.patchpal for custom tools, config, and memory
|
|
688
688
|
- Custom tools work automatically (from ~/.patchpal/tools/ and <repo>/.patchpal/tools/)
|
|
689
|
-
- Auto-sets OLLAMA_CONTEXT_LENGTH for Ollama models:
|
|
690
|
-
* 8192 for regular models (agents)
|
|
691
|
-
* 32768 for reasoning models (gpt-oss, deepseek-r1, qwq, qwen)
|
|
692
689
|
|
|
693
690
|
Recommended for autopilot mode and high-risk operations.
|
|
694
691
|
|
|
@@ -355,15 +355,37 @@ Be comprehensive but concise. The goal is to continue work seamlessly without lo
|
|
|
355
355
|
usage_ratio = total_tokens / self.context_limit
|
|
356
356
|
return usage_ratio >= self.COMPACT_THRESHOLD
|
|
357
357
|
|
|
358
|
-
def get_usage_stats(
|
|
358
|
+
def get_usage_stats(
|
|
359
|
+
self, messages: List[Dict[str, Any]], actual_prompt_tokens: int = None
|
|
360
|
+
) -> Dict[str, Any]:
|
|
359
361
|
"""Get current context usage statistics.
|
|
360
362
|
|
|
361
363
|
Args:
|
|
362
364
|
messages: Current message history
|
|
365
|
+
actual_prompt_tokens: Optional actual prompt tokens from latest API response (includes cache operations)
|
|
363
366
|
|
|
364
367
|
Returns:
|
|
365
368
|
Dict with usage statistics
|
|
366
369
|
"""
|
|
370
|
+
# If we have actual prompt tokens from API (includes cache writes/reads), use those
|
|
371
|
+
if actual_prompt_tokens is not None:
|
|
372
|
+
total_tokens = actual_prompt_tokens + self.output_reserve
|
|
373
|
+
# For display purposes, estimate system vs message breakdown
|
|
374
|
+
system_tokens = self.estimator.estimate_tokens(self.system_prompt)
|
|
375
|
+
datetime_tokens = 30
|
|
376
|
+
message_tokens = actual_prompt_tokens - system_tokens - datetime_tokens
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"system_tokens": system_tokens + datetime_tokens,
|
|
380
|
+
"message_tokens": max(0, message_tokens), # Ensure non-negative
|
|
381
|
+
"output_reserve": self.output_reserve,
|
|
382
|
+
"total_tokens": total_tokens,
|
|
383
|
+
"context_limit": self.context_limit,
|
|
384
|
+
"usage_ratio": total_tokens / self.context_limit,
|
|
385
|
+
"usage_percent": int((total_tokens / self.context_limit) * 100),
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
# Fallback to estimation when actual tokens not available
|
|
367
389
|
system_tokens = self.estimator.estimate_tokens(self.system_prompt)
|
|
368
390
|
datetime_tokens = 30 # Approximate size of dynamic date/time message
|
|
369
391
|
message_tokens = self.estimator.estimate_messages_tokens(messages)
|
|
@@ -67,6 +67,11 @@ class PermissionManager:
|
|
|
67
67
|
These commands replace dedicated tools that were removed (replaced by find tool)
|
|
68
68
|
to reduce redundancy. Since those tools didn't require permissions, their shell
|
|
69
69
|
equivalents shouldn't either.
|
|
70
|
+
|
|
71
|
+
SECURITY NOTE: Environment variable commands (env, printenv, set, Get-Variable)
|
|
72
|
+
are NOT in this list because they can expose API keys and secrets loaded from
|
|
73
|
+
.env files. While we block reading .env files directly, we must also block
|
|
74
|
+
reading the environment variables that were loaded from them.
|
|
70
75
|
"""
|
|
71
76
|
# Check if web tools are enabled
|
|
72
77
|
web_tools_enabled = config.ENABLE_WEB
|
|
@@ -99,9 +104,6 @@ class PermissionManager:
|
|
|
99
104
|
"whereis",
|
|
100
105
|
# Current directory
|
|
101
106
|
"pwd",
|
|
102
|
-
# Environment
|
|
103
|
-
"env",
|
|
104
|
-
"printenv",
|
|
105
107
|
# Network diagnostic
|
|
106
108
|
"ifconfig",
|
|
107
109
|
# Disk/system info
|
|
@@ -132,8 +134,6 @@ class PermissionManager:
|
|
|
132
134
|
"assoc",
|
|
133
135
|
"ftype",
|
|
134
136
|
"doskey /history",
|
|
135
|
-
# Environment
|
|
136
|
-
"set",
|
|
137
137
|
# Network diagnostic
|
|
138
138
|
"tracert",
|
|
139
139
|
"nslookup",
|
|
@@ -169,7 +169,6 @@ class PermissionManager:
|
|
|
169
169
|
"get-host",
|
|
170
170
|
"get-command",
|
|
171
171
|
"get-alias",
|
|
172
|
-
"get-variable",
|
|
173
172
|
"get-member",
|
|
174
173
|
"get-help",
|
|
175
174
|
# Search/filter
|
|
@@ -420,6 +419,19 @@ class PermissionManager:
|
|
|
420
419
|
|
|
421
420
|
# Check if already granted (with full_command for multi-word pattern matching)
|
|
422
421
|
if self._check_existing_grant(tool_name, pattern, full_command):
|
|
422
|
+
# Log that permission was auto-granted from previous session grant
|
|
423
|
+
try:
|
|
424
|
+
from patchpal.tools.audit import log_action_approved
|
|
425
|
+
|
|
426
|
+
log_action_approved(
|
|
427
|
+
tool_name=tool_name,
|
|
428
|
+
description=description,
|
|
429
|
+
approval_type="auto_granted",
|
|
430
|
+
pattern=pattern,
|
|
431
|
+
context={"working_dir": context} if context else None,
|
|
432
|
+
)
|
|
433
|
+
except Exception:
|
|
434
|
+
pass # Don't fail if audit logging fails
|
|
423
435
|
return True
|
|
424
436
|
|
|
425
437
|
# Display the request - use stderr to avoid Rich console capture
|
|
@@ -481,14 +493,53 @@ class PermissionManager:
|
|
|
481
493
|
choice = input("\n\033[1;36mChoice [1-3]:\033[0m ").strip()
|
|
482
494
|
|
|
483
495
|
if choice == "1":
|
|
496
|
+
# Log approval
|
|
497
|
+
try:
|
|
498
|
+
from patchpal.tools.audit import log_action_approved
|
|
499
|
+
|
|
500
|
+
log_action_approved(
|
|
501
|
+
tool_name=tool_name,
|
|
502
|
+
description=description,
|
|
503
|
+
approval_type="user_approved",
|
|
504
|
+
pattern=pattern,
|
|
505
|
+
context={"working_dir": context} if context else None,
|
|
506
|
+
)
|
|
507
|
+
except Exception:
|
|
508
|
+
pass # Don't fail if audit logging fails
|
|
484
509
|
return True
|
|
485
510
|
elif choice == "2":
|
|
486
511
|
# Grant session-only permission (like Claude Code)
|
|
487
512
|
self._grant_permission(tool_name, persistent=False, pattern=pattern)
|
|
513
|
+
# Log approval with session grant
|
|
514
|
+
try:
|
|
515
|
+
from patchpal.tools.audit import log_action_approved
|
|
516
|
+
|
|
517
|
+
log_action_approved(
|
|
518
|
+
tool_name=tool_name,
|
|
519
|
+
description=description,
|
|
520
|
+
approval_type="session_granted",
|
|
521
|
+
pattern=pattern,
|
|
522
|
+
context={"working_dir": context} if context else None,
|
|
523
|
+
)
|
|
524
|
+
except Exception:
|
|
525
|
+
pass # Don't fail if audit logging fails
|
|
488
526
|
return True
|
|
489
527
|
elif choice == "3":
|
|
490
528
|
sys.stderr.write("\n\033[1;31mOperation cancelled.\033[0m\n")
|
|
491
529
|
sys.stderr.flush()
|
|
530
|
+
# Log rejection
|
|
531
|
+
try:
|
|
532
|
+
from patchpal.tools.audit import log_action_blocked
|
|
533
|
+
|
|
534
|
+
log_action_blocked(
|
|
535
|
+
tool_name=tool_name,
|
|
536
|
+
description=description,
|
|
537
|
+
reason="user_rejected",
|
|
538
|
+
pattern=pattern,
|
|
539
|
+
context={"working_dir": context} if context else None,
|
|
540
|
+
)
|
|
541
|
+
except Exception:
|
|
542
|
+
pass # Don't fail if audit logging fails
|
|
492
543
|
return False
|
|
493
544
|
else:
|
|
494
545
|
sys.stderr.write("Invalid choice. Please enter 1, 2, or 3.\n")
|