connectonion 0.6.3__py3-none-any.whl → 0.6.4__py3-none-any.whl
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.
- connectonion/__init__.py +1 -1
- connectonion/cli/co_ai/agent.py +3 -3
- connectonion/cli/co_ai/plugins/__init__.py +2 -3
- connectonion/cli/co_ai/plugins/system_reminder.py +154 -0
- connectonion/cli/co_ai/prompts/system-reminders/agent.md +23 -0
- connectonion/cli/co_ai/prompts/system-reminders/plan_mode.md +13 -0
- connectonion/cli/co_ai/prompts/system-reminders/security.md +14 -0
- connectonion/cli/co_ai/prompts/system-reminders/simplicity.md +14 -0
- connectonion/cli/co_ai/tools/plan_mode.py +1 -4
- connectonion/cli/co_ai/tools/read.py +0 -6
- connectonion/docs/concepts/plugins.md +2 -1
- connectonion/docs/useful_plugins/tool_approval.md +139 -0
- connectonion/useful_plugins/__init__.py +2 -1
- connectonion/useful_plugins/tool_approval.py +233 -0
- {connectonion-0.6.3.dist-info → connectonion-0.6.4.dist-info}/METADATA +1 -1
- {connectonion-0.6.3.dist-info → connectonion-0.6.4.dist-info}/RECORD +18 -15
- connectonion/cli/co_ai/plugins/reminder.py +0 -76
- connectonion/cli/co_ai/plugins/shell_approval.py +0 -105
- connectonion/cli/co_ai/prompts/reminders/plan_mode.md +0 -34
- connectonion/cli/co_ai/reminders.py +0 -159
- {connectonion-0.6.3.dist-info → connectonion-0.6.4.dist-info}/WHEEL +0 -0
- {connectonion-0.6.3.dist-info → connectonion-0.6.4.dist-info}/entry_points.txt +0 -0
connectonion/__init__.py
CHANGED
connectonion/cli/co_ai/agent.py
CHANGED
|
@@ -12,9 +12,9 @@ from .tools import (
|
|
|
12
12
|
load_guide,
|
|
13
13
|
)
|
|
14
14
|
from .skills import skill
|
|
15
|
-
from .plugins import
|
|
15
|
+
from .plugins import system_reminder
|
|
16
16
|
from connectonion import Agent, bash, after_user_input, FileWriter, MODE_AUTO, MODE_NORMAL, TodoList
|
|
17
|
-
from connectonion.useful_plugins import
|
|
17
|
+
from connectonion.useful_plugins import eval, tool_approval
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
PROMPTS_DIR = Path(__file__).parent / "prompts"
|
|
@@ -71,7 +71,7 @@ def create_coding_agent(
|
|
|
71
71
|
if project_context:
|
|
72
72
|
system_prompt += f"\n\n---\n\n{project_context}"
|
|
73
73
|
|
|
74
|
-
plugins = [
|
|
74
|
+
plugins = [eval, system_reminder, tool_approval]
|
|
75
75
|
|
|
76
76
|
agent = Agent(
|
|
77
77
|
name="oo",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
System Reminder Plugin - Injects contextual guidance based on intent and tool usage.
|
|
3
|
+
|
|
4
|
+
Two triggers:
|
|
5
|
+
1. after_user_input: Detect intent (coding, agent creation) and inject relevant reminder
|
|
6
|
+
2. after_each_tool: Inject reminder based on tool usage
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from connectonion.cli.co_ai.plugins.system_reminder import system_reminder
|
|
10
|
+
|
|
11
|
+
agent = Agent("coder", plugins=[system_reminder])
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import fnmatch
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
from connectonion.core.events import after_each_tool, after_user_input
|
|
19
|
+
from connectonion.llm_do import llm_do
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from connectonion.core.agent import Agent
|
|
23
|
+
|
|
24
|
+
# Default reminders directory
|
|
25
|
+
REMINDERS_DIR = Path(__file__).parent.parent / "prompts" / "system-reminders"
|
|
26
|
+
|
|
27
|
+
# Intent detection prompt
|
|
28
|
+
INTENT_PROMPT = """Analyze the user's request.
|
|
29
|
+
|
|
30
|
+
User request: {user_prompt}
|
|
31
|
+
|
|
32
|
+
Is this about building software, creating agents, writing code, or automation?
|
|
33
|
+
Respond with ONE word only: "build" or "other"
|
|
34
|
+
|
|
35
|
+
One word only:"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_frontmatter(text):
|
|
39
|
+
"""Parse YAML frontmatter from markdown."""
|
|
40
|
+
if not text.startswith('---'):
|
|
41
|
+
return {}, text
|
|
42
|
+
parts = text.split('---', 2)
|
|
43
|
+
if len(parts) < 3:
|
|
44
|
+
return {}, text
|
|
45
|
+
import yaml
|
|
46
|
+
return yaml.safe_load(parts[1]) or {}, parts[2].strip()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _load_reminders(reminders_dir):
|
|
50
|
+
"""Load all .md reminder files from directory."""
|
|
51
|
+
reminders_dir = Path(reminders_dir)
|
|
52
|
+
if not reminders_dir.exists():
|
|
53
|
+
return {}
|
|
54
|
+
reminders = {}
|
|
55
|
+
for f in reminders_dir.glob("*.md"):
|
|
56
|
+
meta, body = _parse_frontmatter(f.read_text())
|
|
57
|
+
if meta.get('name'):
|
|
58
|
+
reminders[meta['name']] = {
|
|
59
|
+
'content': body,
|
|
60
|
+
'triggers': meta.get('triggers', []),
|
|
61
|
+
'intent': meta.get('intent'), # New: intent-based trigger
|
|
62
|
+
}
|
|
63
|
+
return reminders
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _matches_pattern(pattern, value):
|
|
67
|
+
"""Check if value matches glob pattern(s)."""
|
|
68
|
+
if not pattern or not value:
|
|
69
|
+
return False
|
|
70
|
+
patterns = [pattern] if isinstance(pattern, str) else pattern
|
|
71
|
+
return any(fnmatch.fnmatch(value, p) for p in patterns)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _find_tool_reminder(reminders, tool_name, args):
|
|
75
|
+
"""Find matching reminder for tool usage."""
|
|
76
|
+
for reminder in reminders.values():
|
|
77
|
+
for trigger in reminder['triggers']:
|
|
78
|
+
if trigger.get('tool') and trigger['tool'] != tool_name:
|
|
79
|
+
continue
|
|
80
|
+
if trigger.get('path_pattern'):
|
|
81
|
+
path = args.get('path') or args.get('file_path', '')
|
|
82
|
+
if not _matches_pattern(trigger['path_pattern'], path):
|
|
83
|
+
continue
|
|
84
|
+
if trigger.get('command_pattern'):
|
|
85
|
+
cmd = args.get('command') or args.get('cmd', '')
|
|
86
|
+
if not _matches_pattern(trigger['command_pattern'], cmd):
|
|
87
|
+
continue
|
|
88
|
+
# All conditions matched
|
|
89
|
+
content = reminder['content']
|
|
90
|
+
path = args.get('path') or args.get('file_path', '')
|
|
91
|
+
return content.replace('${file_path}', path).replace('${tool_name}', tool_name)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _find_intent_reminder(reminders, intent):
|
|
96
|
+
"""Find matching reminder for detected intent."""
|
|
97
|
+
for reminder in reminders.values():
|
|
98
|
+
if reminder.get('intent') == intent:
|
|
99
|
+
return reminder['content']
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Load reminders once at import
|
|
104
|
+
_REMINDERS = _load_reminders(REMINDERS_DIR)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@after_user_input
|
|
108
|
+
def detect_intent(agent: 'Agent') -> None:
|
|
109
|
+
"""Detect user intent and inject relevant system reminder."""
|
|
110
|
+
user_prompt = agent.current_session.get('user_prompt', '')
|
|
111
|
+
if not user_prompt:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
# Use llm_do to detect intent
|
|
115
|
+
intent = llm_do(
|
|
116
|
+
INTENT_PROMPT.format(user_prompt=user_prompt),
|
|
117
|
+
model="co/gemini-2.5-flash",
|
|
118
|
+
temperature=0,
|
|
119
|
+
).strip().lower()
|
|
120
|
+
|
|
121
|
+
# Store intent in session
|
|
122
|
+
agent.current_session['intent'] = intent
|
|
123
|
+
|
|
124
|
+
# Find and inject intent-based reminder
|
|
125
|
+
content = _find_intent_reminder(_REMINDERS, intent)
|
|
126
|
+
if content:
|
|
127
|
+
agent.current_session['messages'].append({
|
|
128
|
+
'role': 'user',
|
|
129
|
+
'content': f"\n\n{content}"
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@after_each_tool
|
|
134
|
+
def inject_tool_reminder(agent: 'Agent') -> None:
|
|
135
|
+
"""Inject matching system reminder into tool result."""
|
|
136
|
+
trace = agent.current_session.get('trace', [])
|
|
137
|
+
messages = agent.current_session.get('messages', [])
|
|
138
|
+
if not trace or not messages:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
last = trace[-1]
|
|
142
|
+
if last.get('type') != 'tool_result':
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
content = _find_tool_reminder(_REMINDERS, last.get('name', ''), last.get('args', {}))
|
|
146
|
+
if content:
|
|
147
|
+
for msg in reversed(messages):
|
|
148
|
+
if msg.get('role') == 'tool':
|
|
149
|
+
msg['content'] = msg.get('content', '') + '\n\n' + content
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Export plugin
|
|
154
|
+
system_reminder = [detect_intent, inject_tool_reminder]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build
|
|
3
|
+
intent: build
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<system-reminder>
|
|
7
|
+
Agent creation detected. Use ConnectOnion framework:
|
|
8
|
+
|
|
9
|
+
**ConnectOnion agents are Python files (.py), NOT markdown files.**
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from connectonion import Agent
|
|
13
|
+
|
|
14
|
+
def list_files(dir: str) -> list[str]: ...
|
|
15
|
+
def get_hash(path: str) -> str: ...
|
|
16
|
+
def delete(path: str) -> str: ...
|
|
17
|
+
|
|
18
|
+
agent = Agent("cleaner", tools=[list_files, get_hash, delete])
|
|
19
|
+
agent.input("Remove duplicate files")
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Give tools, not logic. Let the agent decide strategy. NEVER create standalone scripts with hardcoded logic.**
|
|
23
|
+
</system-reminder>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan-mode
|
|
3
|
+
triggers:
|
|
4
|
+
- tool: enter_plan_mode
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<system-reminder>
|
|
8
|
+
Plan mode is active. You MUST NOT make any edits or run non-readonly tools.
|
|
9
|
+
|
|
10
|
+
Only allowed: glob, grep, read_file, write_plan, exit_plan_mode
|
|
11
|
+
|
|
12
|
+
Write your plan to the plan file, then call exit_plan_mode when done.
|
|
13
|
+
</system-reminder>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security
|
|
3
|
+
triggers:
|
|
4
|
+
- tool: read_file
|
|
5
|
+
path_pattern: ["*.env*", "*credentials*", "*secrets*", "*password*", "*token*", "*.pem", "*.key"]
|
|
6
|
+
- tool: read
|
|
7
|
+
path_pattern: ["*.env*", "*credentials*", "*secrets*", "*password*", "*token*", "*.pem", "*.key"]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<system-reminder>
|
|
11
|
+
This file may contain sensitive information.
|
|
12
|
+
- Never expose secrets in output
|
|
13
|
+
- Never commit real credentials
|
|
14
|
+
</system-reminder>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: simplicity
|
|
3
|
+
triggers:
|
|
4
|
+
- tool: edit
|
|
5
|
+
- tool: multi_edit
|
|
6
|
+
- tool: write
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<system-reminder>
|
|
10
|
+
Keep it simple:
|
|
11
|
+
- Only change what's directly needed
|
|
12
|
+
- Don't add error handling for scenarios that can't happen
|
|
13
|
+
- Three similar lines > premature abstraction
|
|
14
|
+
</system-reminder>
|
|
@@ -6,8 +6,6 @@ from rich.console import Console
|
|
|
6
6
|
from rich.panel import Panel
|
|
7
7
|
from rich.markdown import Markdown
|
|
8
8
|
|
|
9
|
-
from connectonion.cli.co_ai.reminders import REMINDERS
|
|
10
|
-
|
|
11
9
|
console = Console()
|
|
12
10
|
|
|
13
11
|
# Plan mode state (module-level for simplicity)
|
|
@@ -101,8 +99,7 @@ def enter_plan_mode() -> str:
|
|
|
101
99
|
border_style="green"
|
|
102
100
|
))
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
return f"Entered plan mode. Write your plan to {_plan_file_path}, then call exit_plan_mode() when ready for user approval.\n\n{plan_mode_reminder}"
|
|
102
|
+
return f"Entered plan mode. Write your plan to {_plan_file_path}, then call exit_plan_mode() when ready for user approval."
|
|
106
103
|
|
|
107
104
|
|
|
108
105
|
def exit_plan_mode() -> str:
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
from connectonion.cli.co_ai.reminders import inject_reminder, should_show_security_reminder
|
|
7
|
-
|
|
8
6
|
|
|
9
7
|
def read_file(
|
|
10
8
|
path: str,
|
|
@@ -60,8 +58,4 @@ def read_file(
|
|
|
60
58
|
if end < total_lines:
|
|
61
59
|
result += f"\n\n... ({total_lines - end} more lines)"
|
|
62
60
|
|
|
63
|
-
# Inject security reminder for sensitive files
|
|
64
|
-
if should_show_security_reminder(path):
|
|
65
|
-
result = inject_reminder(result, "security")
|
|
66
|
-
|
|
67
61
|
return result
|
|
@@ -54,9 +54,10 @@ agent = Agent("a", plugins=[re_act, logger])
|
|
|
54
54
|
| `eval` | Task evaluation for debugging | [eval.md](../useful_plugins/eval.md) |
|
|
55
55
|
| `image_result_formatter` | Format images for vision models | [image_result_formatter.md](../useful_plugins/image_result_formatter.md) |
|
|
56
56
|
| `shell_approval` | Approve shell commands before execution | [shell_approval.md](../useful_plugins/shell_approval.md) |
|
|
57
|
+
| `tool_approval` | Web-based approval for dangerous tools | [tool_approval.md](../useful_plugins/tool_approval.md) |
|
|
57
58
|
|
|
58
59
|
```python
|
|
59
|
-
from connectonion.useful_plugins import re_act, eval, image_result_formatter, shell_approval
|
|
60
|
+
from connectonion.useful_plugins import re_act, eval, image_result_formatter, shell_approval, tool_approval
|
|
60
61
|
|
|
61
62
|
# Combine plugins
|
|
62
63
|
agent = Agent("assistant", plugins=[re_act, image_result_formatter])
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# tool_approval
|
|
2
|
+
|
|
3
|
+
Web-based approval for dangerous tools via WebSocket. Requires user confirmation before executing tools that can modify files or run commands.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from connectonion import Agent, bash
|
|
9
|
+
from connectonion.useful_plugins import tool_approval
|
|
10
|
+
|
|
11
|
+
agent = Agent("assistant", tools=[bash], plugins=[tool_approval])
|
|
12
|
+
agent.io = my_websocket_io # Required for web mode
|
|
13
|
+
|
|
14
|
+
agent.input("Install dependencies")
|
|
15
|
+
# → Client receives: {"type": "approval_needed", "tool": "bash", "arguments": {"command": "npm install"}}
|
|
16
|
+
# → Client responds: {"approved": true, "scope": "session"}
|
|
17
|
+
# ✓ bash approved (session)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How It Works
|
|
21
|
+
|
|
22
|
+
1. Before each tool executes, check if it's dangerous
|
|
23
|
+
2. If dangerous, send `approval_needed` event via WebSocket
|
|
24
|
+
3. Wait for client response (blocks until received)
|
|
25
|
+
4. If approved: execute tool, optionally remember for session
|
|
26
|
+
5. If rejected: stop batch, return feedback to LLM
|
|
27
|
+
|
|
28
|
+
## Tool Classification
|
|
29
|
+
|
|
30
|
+
### Safe Tools (No Approval)
|
|
31
|
+
|
|
32
|
+
Read-only operations that never modify state:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
read, read_file, glob, grep, search
|
|
36
|
+
list_files, get_file_info, task, load_guide
|
|
37
|
+
enter_plan_mode, exit_plan_mode, write_plan
|
|
38
|
+
task_output, ask_user
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Dangerous Tools (Require Approval)
|
|
42
|
+
|
|
43
|
+
Operations that can modify files or have side effects:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
bash, shell, run, run_in_dir
|
|
47
|
+
write, edit, multi_edit
|
|
48
|
+
run_background, kill_task
|
|
49
|
+
send_email, post, delete, remove
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Client Protocol
|
|
53
|
+
|
|
54
|
+
### Receive from server
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"type": "approval_needed",
|
|
59
|
+
"tool": "bash",
|
|
60
|
+
"arguments": {"command": "npm install"}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Send response
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
// Approve for this session (no re-prompting)
|
|
68
|
+
{"approved": true, "scope": "session"}
|
|
69
|
+
|
|
70
|
+
// Approve once only
|
|
71
|
+
{"approved": true, "scope": "once"}
|
|
72
|
+
|
|
73
|
+
// Reject with feedback
|
|
74
|
+
{"approved": false, "feedback": "Use yarn instead"}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Approval Scopes
|
|
78
|
+
|
|
79
|
+
| Scope | Behavior |
|
|
80
|
+
|-------|----------|
|
|
81
|
+
| `once` | Approve this call only |
|
|
82
|
+
| `session` | Approve for rest of session (stored in memory) |
|
|
83
|
+
|
|
84
|
+
## Rejection Behavior
|
|
85
|
+
|
|
86
|
+
When user rejects a tool:
|
|
87
|
+
|
|
88
|
+
1. Raises `ValueError` with feedback message
|
|
89
|
+
2. Stops the entire tool batch (remaining tools skipped)
|
|
90
|
+
3. LLM receives the error and can adjust approach
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# Example error message
|
|
94
|
+
"User rejected tool 'bash'. Feedback: Use yarn instead"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Terminal Logging
|
|
98
|
+
|
|
99
|
+
The plugin logs all approval decisions:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
✓ bash approved (session) # Approved with session scope
|
|
103
|
+
✓ edit approved (once) # Approved for single use
|
|
104
|
+
⏭ bash (session-approved) # Skipped (already approved)
|
|
105
|
+
✗ bash rejected: Use yarn # Rejected with feedback
|
|
106
|
+
✗ bash - connection closed # WebSocket closed
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Events
|
|
110
|
+
|
|
111
|
+
| Handler | Event | Purpose |
|
|
112
|
+
|---------|-------|---------|
|
|
113
|
+
| `check_approval` | `before_each_tool` | Check approval and prompt client |
|
|
114
|
+
|
|
115
|
+
## Session Data
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Approval state stored in session
|
|
119
|
+
agent.current_session['approval'] = {
|
|
120
|
+
'approved_tools': {
|
|
121
|
+
'bash': 'session', # Approved for session
|
|
122
|
+
'write': 'session' # Approved for session
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Non-Web Mode
|
|
128
|
+
|
|
129
|
+
When `agent.io` is None (not web mode), all tools execute without approval. This is the default behavior for CLI usage.
|
|
130
|
+
|
|
131
|
+
## Unknown Tools
|
|
132
|
+
|
|
133
|
+
Tools not in SAFE_TOOLS or DANGEROUS_TOOLS are treated as safe and execute without approval.
|
|
134
|
+
|
|
135
|
+
## See Also
|
|
136
|
+
|
|
137
|
+
- [shell_approval](shell_approval.md) - Terminal-based approval for shell commands
|
|
138
|
+
- [Events](../concepts/events.md) - Available event hooks
|
|
139
|
+
- [Plugins](../concepts/plugins.md) - Plugin system overview
|
|
@@ -18,5 +18,6 @@ from .gmail_plugin import gmail_plugin
|
|
|
18
18
|
from .calendar_plugin import calendar_plugin
|
|
19
19
|
from .ui_stream import ui_stream
|
|
20
20
|
from .system_reminder import system_reminder
|
|
21
|
+
from .tool_approval import tool_approval
|
|
21
22
|
|
|
22
|
-
__all__ = ['re_act', 'eval', 'image_result_formatter', 'shell_approval', 'gmail_plugin', 'calendar_plugin', 'ui_stream', 'system_reminder']
|
|
23
|
+
__all__ = ['re_act', 'eval', 'image_result_formatter', 'shell_approval', 'gmail_plugin', 'calendar_plugin', 'ui_stream', 'system_reminder', 'tool_approval']
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Web-based tool approval plugin - request user approval before dangerous tools
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [core/events.py] | imported by [useful_plugins/__init__.py] | tested by [tests/unit/test_tool_approval.py]
|
|
5
|
+
Data flow: before_each_tool fires → check if dangerous tool → io.send(approval_needed) → io.receive() blocks → approved: continue, rejected: raise ValueError
|
|
6
|
+
State/Effects: stores approved_tools in session for "session" scope approvals | blocks on io.receive() until client responds | logs all approval decisions via agent.logger
|
|
7
|
+
Integration: exposes tool_approval plugin list | uses agent.io for WebSocket communication | requires client to handle "approval_needed" events
|
|
8
|
+
Errors: raises ValueError on rejection (stops batch, feedback sent to LLM)
|
|
9
|
+
|
|
10
|
+
Tool Approval Plugin - Request client approval before executing dangerous tools.
|
|
11
|
+
|
|
12
|
+
WebSocket-only. Uses io.send/receive pattern:
|
|
13
|
+
1. Sends {type: "approval_needed", tool, arguments} to client
|
|
14
|
+
2. Blocks until client responds with {approved: bool, scope?, feedback?}
|
|
15
|
+
3. If approved: execute tool (optionally save to session memory)
|
|
16
|
+
4. If rejected: raise ValueError, stopping batch, LLM sees feedback
|
|
17
|
+
|
|
18
|
+
Tool Classification:
|
|
19
|
+
- SAFE_TOOLS: Read-only operations (read, glob, grep, etc.) - never need approval
|
|
20
|
+
- DANGEROUS_TOOLS: Write/execute operations (bash, write, edit, etc.) - always need approval
|
|
21
|
+
- Unknown tools: Treated as safe (no approval needed)
|
|
22
|
+
|
|
23
|
+
Session Memory:
|
|
24
|
+
- scope="once": Approve for this call only
|
|
25
|
+
- scope="session": Approve for rest of session (no re-prompting)
|
|
26
|
+
|
|
27
|
+
Rejection Behavior:
|
|
28
|
+
- Raises ValueError with user feedback
|
|
29
|
+
- Stops entire tool batch (remaining tools skipped)
|
|
30
|
+
- LLM receives error message and can adjust approach
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
from connectonion import Agent
|
|
34
|
+
from connectonion.useful_plugins import tool_approval
|
|
35
|
+
|
|
36
|
+
agent = Agent("assistant", tools=[bash, write], plugins=[tool_approval])
|
|
37
|
+
|
|
38
|
+
Client Protocol:
|
|
39
|
+
# Receive from server:
|
|
40
|
+
{"type": "approval_needed", "tool": "bash", "arguments": {"command": "npm install"}}
|
|
41
|
+
|
|
42
|
+
# Send response:
|
|
43
|
+
{"approved": true, "scope": "session"} # Approve for session
|
|
44
|
+
{"approved": true, "scope": "once"} # Approve once
|
|
45
|
+
{"approved": false, "feedback": "Use yarn instead"} # Reject with feedback
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from typing import TYPE_CHECKING
|
|
49
|
+
|
|
50
|
+
from ..core.events import before_each_tool
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from ..core.agent import Agent
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Tools that NEVER need approval (read-only, safe)
|
|
57
|
+
# These tools cannot modify system state or have external side effects.
|
|
58
|
+
# Add new read-only tools here to skip approval prompts.
|
|
59
|
+
SAFE_TOOLS = {
|
|
60
|
+
# File reading - read contents without modification
|
|
61
|
+
'read', 'read_file',
|
|
62
|
+
# Search operations - find files/content without modification
|
|
63
|
+
'glob', 'grep', 'search',
|
|
64
|
+
# Info operations - query metadata only
|
|
65
|
+
'list_files', 'get_file_info',
|
|
66
|
+
# Agent operations - sub-agents handle their own approval
|
|
67
|
+
'task',
|
|
68
|
+
# Documentation - load reference materials
|
|
69
|
+
'load_guide',
|
|
70
|
+
# Planning - state management without side effects
|
|
71
|
+
'enter_plan_mode', 'exit_plan_mode', 'write_plan',
|
|
72
|
+
# Task management - read-only task status
|
|
73
|
+
'task_output',
|
|
74
|
+
# User interaction - prompts user, not system modification
|
|
75
|
+
'ask_user',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Tools that ALWAYS need approval (destructive/side-effects)
|
|
79
|
+
# These tools can modify files, execute code, or have external effects.
|
|
80
|
+
# User approval required before execution in web mode.
|
|
81
|
+
DANGEROUS_TOOLS = {
|
|
82
|
+
# Shell execution - arbitrary command execution
|
|
83
|
+
'bash', 'shell', 'run', 'run_in_dir',
|
|
84
|
+
# File modification - write/edit file contents
|
|
85
|
+
'write', 'edit', 'multi_edit',
|
|
86
|
+
# Background tasks - long-running command execution
|
|
87
|
+
'run_background',
|
|
88
|
+
# Task control - terminate running processes
|
|
89
|
+
'kill_task',
|
|
90
|
+
# External communication - send data outside system
|
|
91
|
+
'send_email', 'post',
|
|
92
|
+
# Deletion - remove files/resources
|
|
93
|
+
'delete', 'remove',
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Session state helpers for approval memory
|
|
98
|
+
# These functions manage the session['approval'] dict which tracks
|
|
99
|
+
# which tools have been approved for the current session.
|
|
100
|
+
|
|
101
|
+
def _init_approval_state(session: dict) -> None:
|
|
102
|
+
"""Initialize approval state in session if not present.
|
|
103
|
+
|
|
104
|
+
Creates session['approval']['approved_tools'] dict for storing
|
|
105
|
+
tool approvals with scope='session'.
|
|
106
|
+
"""
|
|
107
|
+
if 'approval' not in session:
|
|
108
|
+
session['approval'] = {
|
|
109
|
+
'approved_tools': {}, # tool_name -> 'session'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _is_approved_for_session(session: dict, tool_name: str) -> bool:
|
|
114
|
+
"""Check if tool was approved for this session.
|
|
115
|
+
|
|
116
|
+
Returns True if user previously approved this tool with scope='session'.
|
|
117
|
+
"""
|
|
118
|
+
approval = session.get('approval', {})
|
|
119
|
+
return approval.get('approved_tools', {}).get(tool_name) == 'session'
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _save_session_approval(session: dict, tool_name: str) -> None:
|
|
123
|
+
"""Save tool as approved for this session.
|
|
124
|
+
|
|
125
|
+
Future calls to the same tool will skip approval prompts.
|
|
126
|
+
"""
|
|
127
|
+
_init_approval_state(session)
|
|
128
|
+
session['approval']['approved_tools'][tool_name] = 'session'
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _log(agent: 'Agent', message: str, style: str = None) -> None:
|
|
132
|
+
"""Log message via agent's logger if available.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
agent: Agent instance
|
|
136
|
+
message: Message to log
|
|
137
|
+
style: Rich style string (e.g., "[green]", "[red]")
|
|
138
|
+
"""
|
|
139
|
+
if hasattr(agent, 'logger') and agent.logger:
|
|
140
|
+
agent.logger.print(message, style)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@before_each_tool
|
|
144
|
+
def check_approval(agent: 'Agent') -> None:
|
|
145
|
+
"""Check if tool needs approval and request from client.
|
|
146
|
+
|
|
147
|
+
Flow:
|
|
148
|
+
1. Skip if no IO (not web mode)
|
|
149
|
+
2. Skip if safe tool
|
|
150
|
+
3. Skip if unknown tool (default: safe)
|
|
151
|
+
4. Skip if already approved for session
|
|
152
|
+
5. Send approval_needed, wait for response
|
|
153
|
+
6. If approved: optionally save to session, continue
|
|
154
|
+
7. If rejected: raise ValueError (stops batch)
|
|
155
|
+
|
|
156
|
+
Logging:
|
|
157
|
+
- Logs approval requests, approvals, and rejections
|
|
158
|
+
- Uses agent.logger.print() for terminal output
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ValueError: If user rejects the tool (includes feedback if provided)
|
|
162
|
+
"""
|
|
163
|
+
# No IO = not web mode, skip
|
|
164
|
+
if not agent.io:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Get pending tool info
|
|
168
|
+
pending = agent.current_session.get('pending_tool')
|
|
169
|
+
if not pending:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
tool_name = pending['name']
|
|
173
|
+
tool_args = pending['arguments']
|
|
174
|
+
|
|
175
|
+
# Safe tools don't need approval
|
|
176
|
+
if tool_name in SAFE_TOOLS:
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# Unknown tools (not in SAFE or DANGEROUS) are treated as safe
|
|
180
|
+
if tool_name not in DANGEROUS_TOOLS:
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
# Already approved for this session
|
|
184
|
+
if _is_approved_for_session(agent.current_session, tool_name):
|
|
185
|
+
_log(agent, f"[dim]⏭ {tool_name} (session-approved)[/dim]")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
# Send approval request to client
|
|
189
|
+
agent.io.send({
|
|
190
|
+
'type': 'approval_needed',
|
|
191
|
+
'tool': tool_name,
|
|
192
|
+
'arguments': tool_args,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
# Wait for client response (BLOCKS)
|
|
196
|
+
response = agent.io.receive()
|
|
197
|
+
|
|
198
|
+
# Handle connection closed
|
|
199
|
+
if response.get('type') == 'io_closed':
|
|
200
|
+
_log(agent, f"[red]✗ {tool_name} - connection closed[/red]")
|
|
201
|
+
raise ValueError(f"Connection closed while waiting for approval of '{tool_name}'")
|
|
202
|
+
|
|
203
|
+
# Check approval
|
|
204
|
+
approved = response.get('approved', False)
|
|
205
|
+
|
|
206
|
+
if approved:
|
|
207
|
+
# Save to session if scope is "session"
|
|
208
|
+
scope = response.get('scope', 'once')
|
|
209
|
+
if scope == 'session':
|
|
210
|
+
_save_session_approval(agent.current_session, tool_name)
|
|
211
|
+
_log(agent, f"[green]✓ {tool_name} approved (session)[/green]")
|
|
212
|
+
else:
|
|
213
|
+
_log(agent, f"[green]✓ {tool_name} approved (once)[/green]")
|
|
214
|
+
# Continue to execute tool
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Rejected - raise ValueError to stop batch
|
|
218
|
+
feedback = response.get('feedback', '')
|
|
219
|
+
if feedback:
|
|
220
|
+
_log(agent, f"[red]✗ {tool_name} rejected: {feedback}[/red]")
|
|
221
|
+
else:
|
|
222
|
+
_log(agent, f"[red]✗ {tool_name} rejected[/red]")
|
|
223
|
+
|
|
224
|
+
error_msg = f"User rejected tool '{tool_name}'."
|
|
225
|
+
if feedback:
|
|
226
|
+
error_msg += f" Feedback: {feedback}"
|
|
227
|
+
raise ValueError(error_msg)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Export as plugin (list of event handlers)
|
|
231
|
+
# Usage: Agent("name", plugins=[tool_approval])
|
|
232
|
+
# The plugin registers check_approval as a before_each_tool handler
|
|
233
|
+
tool_approval = [check_approval]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: connectonion
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: A simple Python framework for creating AI agents with behavior tracking
|
|
5
5
|
Project-URL: Homepage, https://github.com/openonion/connectonion
|
|
6
6
|
Project-URL: Documentation, https://docs.connectonion.com
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
connectonion/__init__.py,sha256=
|
|
1
|
+
connectonion/__init__.py,sha256=rlIzfNx6-3ijsVefkkZHtF_3oqRHmDekaEDoJXBkoLY,3416
|
|
2
2
|
connectonion/address.py,sha256=YOzpMOej-HqJUE6o0i0fG8rB7HM-Iods36s9OD--5ig,10852
|
|
3
3
|
connectonion/console.py,sha256=Gl0K0c3ZHlLkbGlBVx0Wgb5Fg8LNVci9WQhSDDdGmJg,21937
|
|
4
4
|
connectonion/llm_do.py,sha256=rwgSsTreNGAq5xV3m9lbA7U5AE0XOZNdihJwW5FHz0k,12005
|
|
@@ -19,10 +19,9 @@ connectonion/cli/browser_agent/prompts/form_filler.md,sha256=r4Trnln51rjKTIYGJ9S
|
|
|
19
19
|
connectonion/cli/browser_agent/prompts/scroll_strategy.md,sha256=fbvEtMM4J9yhGXNeDdKCf4D5ZB5fA-KQrLapmul0wVU,833
|
|
20
20
|
connectonion/cli/browser_agent/scripts/extract_elements.js,sha256=0YMufRSeBf6PQLxbpgVmHvlnjPVNTYqmmsuWyOZBGNc,4651
|
|
21
21
|
connectonion/cli/co_ai/__init__.py,sha256=rxHdQFxV3iH9y60VhuoKu_jly02JHbdQEHAkpMZPrIM,183
|
|
22
|
-
connectonion/cli/co_ai/agent.py,sha256=
|
|
22
|
+
connectonion/cli/co_ai/agent.py,sha256=4xVunKfK9s61TOrn0nDpFn2_j627fRi9K_EJlLtx5Rg,2174
|
|
23
23
|
connectonion/cli/co_ai/context.py,sha256=B07GMFUFqAP-_76PxJigHgj7AMkbMBX0kAObysCz5tM,3778
|
|
24
24
|
connectonion/cli/co_ai/main.py,sha256=k9SvRrs2nFLBOWCrqjULfxWKwpcRxwiqvDkNxWEwvtA,1480
|
|
25
|
-
connectonion/cli/co_ai/reminders.py,sha256=x5C14uFQWEP6L1T15-zzS5jVLCoIDfRJGsS2lc0Oo7Q,4898
|
|
26
25
|
connectonion/cli/co_ai/sessions.py,sha256=YbkTowHw2BpSIh2sGg0avnJLuwObkKeFZ0UXGkXpLlg,3526
|
|
27
26
|
connectonion/cli/co_ai/agents/__init__.py,sha256=k9KdUsmsG80Us4_SnJ4m9Ibx0KNVQ_ly0JlNFjcX1gc,142
|
|
28
27
|
connectonion/cli/co_ai/agents/registry.py,sha256=-hhgh6S9iufwKoxT-kO3mhN-wLYSlB_YJVxoK3I4598,1740
|
|
@@ -35,9 +34,8 @@ connectonion/cli/co_ai/commands/init.py,sha256=w_SoRNU8CwULlIvWkRwxNcItb9XnSWKw0
|
|
|
35
34
|
connectonion/cli/co_ai/commands/sessions.py,sha256=4F-s86wgd_xbSshz2G4O7Kfgzz61fwv3JLmYjuJbonE,1667
|
|
36
35
|
connectonion/cli/co_ai/commands/tasks.py,sha256=BoxyrFSAb4uKAiZRAu1u3f0UjepCD-Es3jSDQlENFA4,1767
|
|
37
36
|
connectonion/cli/co_ai/commands/undo.py,sha256=WE_TA4Td0uSBaHZcyW_kxYI59nmpFcrrSSDVCRq8BJg,2736
|
|
38
|
-
connectonion/cli/co_ai/plugins/__init__.py,sha256=
|
|
39
|
-
connectonion/cli/co_ai/plugins/
|
|
40
|
-
connectonion/cli/co_ai/plugins/shell_approval.py,sha256=uolVMC6JCJGTcyaDq8UvQ_m-OaCCYZlagU4cckAqkW0,4097
|
|
37
|
+
connectonion/cli/co_ai/plugins/__init__.py,sha256=vC3R3b8JDJ9KICFlRsOPmE3XTpnvOmuz5t-XW8icMI8,101
|
|
38
|
+
connectonion/cli/co_ai/plugins/system_reminder.py,sha256=XSVmljS4_cOKzayOrBcv9JtNx03I40u4ZD8mw9Zj1SM,4889
|
|
41
39
|
connectonion/cli/co_ai/prompts/assembler.py,sha256=ubhS2cT68z0PqdXdjlzIRVodX3o7iTvg0XCu0vHUDxM,9742
|
|
42
40
|
connectonion/cli/co_ai/prompts/main.md,sha256=Dq5F32HLpFwrM8jKB53hWX_Ztw4qggRxRtFausnsZE8,8591
|
|
43
41
|
connectonion/cli/co_ai/prompts/summarization.md,sha256=Qu5T8qWU7ZtLUSgJb_Ga1_vYt7on9_dp1MnFMCyg41c,1979
|
|
@@ -153,7 +151,10 @@ connectonion/cli/co_ai/prompts/connectonion/useful_tools/slash_command.md,sha256
|
|
|
153
151
|
connectonion/cli/co_ai/prompts/connectonion/useful_tools/terminal.md,sha256=BdPz0cnsD8JkMLs5zhSPlKQbhV0vIXWbDfrxnkT12IM,1751
|
|
154
152
|
connectonion/cli/co_ai/prompts/connectonion/useful_tools/todo_list.md,sha256=8qQrdtlNnGip1oocDYEGMItomOffFKHZ6xGXYDgN6-Y,5266
|
|
155
153
|
connectonion/cli/co_ai/prompts/connectonion/useful_tools/web_fetch.md,sha256=uMnlsMHbUfh2M1Vnaf8aaMnveMTuatnpd92r2sn-uIo,2241
|
|
156
|
-
connectonion/cli/co_ai/prompts/reminders/
|
|
154
|
+
connectonion/cli/co_ai/prompts/system-reminders/agent.md,sha256=4Yifx5W1kuQVZ8qA3Kwpf-bwi_F-zCaf6jjMJoT1FwA,569
|
|
155
|
+
connectonion/cli/co_ai/prompts/system-reminders/plan_mode.md,sha256=Qh5P7LapxAw9NBYjRWtgBNgJ2sTepSErL85ruCQXiIA,310
|
|
156
|
+
connectonion/cli/co_ai/prompts/system-reminders/security.md,sha256=WlLWakeEUR_w9ZOVg8AiHuGX4QO-BaB45AJ06tgcPbc,420
|
|
157
|
+
connectonion/cli/co_ai/prompts/system-reminders/simplicity.md,sha256=kKqz_Op9A6Dh35UHHxiUdEY2En63Z-R2tNrYwLi--KY,283
|
|
157
158
|
connectonion/cli/co_ai/prompts/tools/ask_user.md,sha256=H3YwaxdXy9uxxPIcTlbOp9wjl-0ZXnIRuvrTOzIKpdM,1458
|
|
158
159
|
connectonion/cli/co_ai/prompts/tools/background.md,sha256=ipJFKe9NM61EvL0QoNYpzAbmRwsSrV2Q_C2puj9ZKsE,1400
|
|
159
160
|
connectonion/cli/co_ai/prompts/tools/edit.md,sha256=2cxl-IID6tItdeASQxgcKtoRLVvYWNLafhfGjIFW_Rc,2500
|
|
@@ -179,8 +180,8 @@ connectonion/cli/co_ai/tools/glob.py,sha256=GrX7ozs61mkf8uG4E5Pi3w8aPP8QYrLnC5Pj
|
|
|
179
180
|
connectonion/cli/co_ai/tools/grep.py,sha256=5VXMbFE-uY_JVAXTOevpTTzeB8oXw7XsdawM5TLqN2s,5063
|
|
180
181
|
connectonion/cli/co_ai/tools/load_guide.py,sha256=tAomLc21O_UJIieRdX4L1oUTOMcZBIO4XoDyhcMe1PA,617
|
|
181
182
|
connectonion/cli/co_ai/tools/multi_edit.py,sha256=BNmkWJRhuM-W4WW-c8lVFo3zbIIvoadjfeunD_nO7xY,4423
|
|
182
|
-
connectonion/cli/co_ai/tools/plan_mode.py,sha256=
|
|
183
|
-
connectonion/cli/co_ai/tools/read.py,sha256
|
|
183
|
+
connectonion/cli/co_ai/tools/plan_mode.py,sha256=enyynOtXb7hS4xZZaNuSJAMZ22oXCrH0sItFdyBp1v8,4910
|
|
184
|
+
connectonion/cli/co_ai/tools/read.py,sha256=-I-5i01m7fgmQBXlfhGUyW61SLszaVJpNarGU9Z2qrw,1650
|
|
184
185
|
connectonion/cli/co_ai/tools/task.py,sha256=-65KJl4tdo0l-aeRIL-AXHKCKMBG9x6z2gAau-AfUHE,1817
|
|
185
186
|
connectonion/cli/co_ai/tools/todo_list.py,sha256=t2uQICQELM_dDCJcD2A0basxbMYV9bnX_nDbqQlKahk,4983
|
|
186
187
|
connectonion/cli/co_ai/tools/write.py,sha256=nSO-ASVZXy4acj40QZ3afVYHSYaFaL-OcqPdYQaQlbw,3577
|
|
@@ -277,7 +278,7 @@ connectonion/tui/providers.py,sha256=7e3PXQv6xtasSSne6PEUAz8yEG430uN9FAafioALbEo
|
|
|
277
278
|
connectonion/tui/status_bar.py,sha256=MUJICFKp4fm2Hres2mt5dbVsw1OwssbuI-KuzwY1fZA,5479
|
|
278
279
|
connectonion/useful_events_handlers/__init__.py,sha256=V2iLrD_ryP6ubIKmN3HwsU-9OI-O1y76m747u92-RWc,826
|
|
279
280
|
connectonion/useful_events_handlers/reflect.py,sha256=z6BGx7JuzhG0AXc0XtJn83YG3KVxecQAwMQcSyfbRbs,4653
|
|
280
|
-
connectonion/useful_plugins/__init__.py,sha256=
|
|
281
|
+
connectonion/useful_plugins/__init__.py,sha256=8uiZ1jpNzoHjePaQQEKF4KJ1u8jLiHyN1Kik-_gyft0,1477
|
|
281
282
|
connectonion/useful_plugins/calendar_plugin.py,sha256=PoQoOfLcprDDBRrt1Ykzlh2RDiOofIyL7tO-hERkYV8,6004
|
|
282
283
|
connectonion/useful_plugins/eval.py,sha256=6uJn2mZZiJpMQ1e-6Nw042wdYSioFrRMHA-MZZeT388,4932
|
|
283
284
|
connectonion/useful_plugins/gmail_plugin.py,sha256=94H31zWOjwAuiDMdgg5tnlu-1I9yxkM0ZU42w2YdJOI,5702
|
|
@@ -285,6 +286,7 @@ connectonion/useful_plugins/image_result_formatter.py,sha256=sYcyn3L3YvC24Mnu3Mk
|
|
|
285
286
|
connectonion/useful_plugins/re_act.py,sha256=q1l99zGtKmK6SGxLFGeaCM3v274fXfWOHwHoXLgZKVY,5828
|
|
286
287
|
connectonion/useful_plugins/shell_approval.py,sha256=ytmwXpgvjX0VLABsY7XyTpKiB_68uA2MUzS9vVwLJws,6361
|
|
287
288
|
connectonion/useful_plugins/system_reminder.py,sha256=eLeOyGDyNH_TBlQblRg5CJ3XLhh0jaWrGPSD0hjZ1Ag,3338
|
|
289
|
+
connectonion/useful_plugins/tool_approval.py,sha256=P403MLpQBdDO7Loz3wVlXtAhVGNaj7frMB5NKOdDuyc,8334
|
|
288
290
|
connectonion/useful_plugins/ui_stream.py,sha256=Jsh5URgIGBLJAmLnRne7XikRoI0x2TePt0DrhU7yYWA,2152
|
|
289
291
|
connectonion/useful_prompts/README.md,sha256=jMIjzxjyu5zc1Mk8KAUvZlN9iroiznckbOn9_sLjMBo,1636
|
|
290
292
|
connectonion/useful_prompts/__init__.py,sha256=ps5sON_kafzx9nt8KqLQF0QDoFSOYAhrlxFshS5zXlI,1795
|
|
@@ -375,7 +377,7 @@ connectonion/docs/concepts/events.md,sha256=AwVddkW7ZqkFWr1ie8Z5GfSUE1xzyCF3j_Y6
|
|
|
375
377
|
connectonion/docs/concepts/llm_do.md,sha256=1Ns_k77RAAGBRvz7Z9_5c4IsEgSUYx3_EthSewV9dvI,6759
|
|
376
378
|
connectonion/docs/concepts/max_iterations.md,sha256=EUEMjQi2Lvv8Ab_maONIo3hM4_0SU1W7p22E87tN1uc,10782
|
|
377
379
|
connectonion/docs/concepts/models.md,sha256=rohjOX_eT2cu1jsvQxIOpVDAvX6tinqHMCfgZCGt6zY,20113
|
|
378
|
-
connectonion/docs/concepts/plugins.md,sha256=
|
|
380
|
+
connectonion/docs/concepts/plugins.md,sha256=13MjQF3QObY0Avn4lQg6-2ZarzBE3MN-n9Z-61Ng-zY,2784
|
|
379
381
|
connectonion/docs/concepts/prompts.md,sha256=LXiyNSniGrD0PZeEkadmzR9Fc7qVR8hj01KZCIklSyU,3191
|
|
380
382
|
connectonion/docs/concepts/session.md,sha256=7wPqsvm3DDNuV2yBg1LqdHe83ZCL5qR2julWc8-jCxQ,10573
|
|
381
383
|
connectonion/docs/concepts/tools.md,sha256=2y8Bi31UqQHmDFf8dbhm7vYweWcPCl4ebYkicGFKJuU,14518
|
|
@@ -446,6 +448,7 @@ connectonion/docs/useful_plugins/image_result_formatter.md,sha256=FucmJjccl9MuY5
|
|
|
446
448
|
connectonion/docs/useful_plugins/re_act.md,sha256=QWC6kB6R2foOpLZuO0Hll4FhPDdxtM82kWYenRkuMj8,2345
|
|
447
449
|
connectonion/docs/useful_plugins/shell_approval.md,sha256=OA4cZZRB8ueJQrjrLvk_4W2LBI1FP16tfJBI8DGqf8U,1776
|
|
448
450
|
connectonion/docs/useful_plugins/system_reminder.md,sha256=Hm-vxTD5A2fgoodufSdmkVSDZecP-na5ylFBZAXRvCI,5509
|
|
451
|
+
connectonion/docs/useful_plugins/tool_approval.md,sha256=fiZbrXTH1UOXH95Yfrjc1OWJ5HNbylOiLXP-sF_6amo,3482
|
|
449
452
|
connectonion/docs/useful_prompts/README.md,sha256=Wln15T2FMLCMFqXAU7g4se2lQVSQcCAV2CLrK7sJDNM,3062
|
|
450
453
|
connectonion/docs/useful_prompts/coding_agent.md,sha256=1MQLjKKzRANUWE5hc5csvOgHHwLzSY_bfQRlB-w6dEE,4571
|
|
451
454
|
connectonion/docs/useful_tools/README.md,sha256=v1HZRzkeXCLmNwlDIGQK6dZEXJQ2WV11U9LoLMD_5Xg,2572
|
|
@@ -463,7 +466,7 @@ connectonion/docs/useful_tools/slash_command.md,sha256=B4jTn9Bck19rVdecZCUqMSUdc
|
|
|
463
466
|
connectonion/docs/useful_tools/terminal.md,sha256=SeAt2BNN_91dVuxIHpFbT9V_i8XPTCR4-U2UX1_05RU,2041
|
|
464
467
|
connectonion/docs/useful_tools/todo_list.md,sha256=4MGHUYYamYXV_NfYi179IvmKuAdxBW14nD1g4PrLjl4,5537
|
|
465
468
|
connectonion/docs/useful_tools/web_fetch.md,sha256=uri7ZjhJ8rFgy3SNolzGMlXTHGEBCIUHWlwpXKWqEDw,2516
|
|
466
|
-
connectonion-0.6.
|
|
467
|
-
connectonion-0.6.
|
|
468
|
-
connectonion-0.6.
|
|
469
|
-
connectonion-0.6.
|
|
469
|
+
connectonion-0.6.4.dist-info/METADATA,sha256=x8-IEs4md_WjbpnMM1q6sGv4AnO_0H6vPARWuGjq_To,22190
|
|
470
|
+
connectonion-0.6.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
471
|
+
connectonion-0.6.4.dist-info/entry_points.txt,sha256=XDB-kVN7Qgy4DmYTkjQB_O6hZeUND-SqmZbdoQPn6WA,90
|
|
472
|
+
connectonion-0.6.4.dist-info/RECORD,,
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Reminder plugin - injects contextual reminders into tool results.
|
|
3
|
-
|
|
4
|
-
Like Claude Code's system reminders, these are appended to tool results
|
|
5
|
-
(not separate messages) to guide agent behavior without extra API calls.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
from connectonion.cli.co_ai.plugins.reminder import reminder_plugin
|
|
9
|
-
|
|
10
|
-
agent = Agent("coder", plugins=[reminder_plugin])
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from connectonion.core.events import after_each_tool
|
|
14
|
-
from ..reminders import REMINDERS, should_show_security_reminder
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _get_reminder_for_tool(tool_name: str, args: dict, result: str) -> str | None:
|
|
18
|
-
"""Determine which reminder to inject based on tool and context."""
|
|
19
|
-
|
|
20
|
-
# write_file with .py extension → remind about ConnectOnion pattern
|
|
21
|
-
if tool_name == "write_file":
|
|
22
|
-
path = args.get("path", "") or args.get("file_path", "")
|
|
23
|
-
if path.endswith(".py"):
|
|
24
|
-
return "connectonion_workflow"
|
|
25
|
-
|
|
26
|
-
# read_file with sensitive path → security reminder
|
|
27
|
-
if tool_name in ("read_file", "read"):
|
|
28
|
-
path = args.get("path", "") or args.get("file_path", "")
|
|
29
|
-
if should_show_security_reminder(path):
|
|
30
|
-
return "security"
|
|
31
|
-
|
|
32
|
-
# bash/shell commands that modify code
|
|
33
|
-
if tool_name in ("bash", "shell", "run_command"):
|
|
34
|
-
cmd = args.get("command", "") or args.get("cmd", "")
|
|
35
|
-
# If creating/editing Python files
|
|
36
|
-
if any(x in cmd for x in [">.py", ">> .py", "cat >", "echo >", "sed -i"]):
|
|
37
|
-
return "connectonion_workflow"
|
|
38
|
-
|
|
39
|
-
return None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def inject_reminder_handler(agent):
|
|
43
|
-
"""Inject contextual reminders into tool results.
|
|
44
|
-
|
|
45
|
-
This handler runs after each tool execution and modifies the
|
|
46
|
-
tool result message to include relevant reminders.
|
|
47
|
-
"""
|
|
48
|
-
trace = agent.current_session.get('trace', [])
|
|
49
|
-
messages = agent.current_session.get('messages', [])
|
|
50
|
-
|
|
51
|
-
if not trace or not messages:
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
# Get the most recent tool execution
|
|
55
|
-
last_trace = trace[-1]
|
|
56
|
-
if last_trace.get('type') != 'tool_result':
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
tool_name = last_trace.get('name', '')
|
|
60
|
-
tool_args = last_trace.get('args', {})
|
|
61
|
-
result = last_trace.get('result', '')
|
|
62
|
-
|
|
63
|
-
# Determine which reminder to inject
|
|
64
|
-
reminder_key = _get_reminder_for_tool(tool_name, tool_args, result)
|
|
65
|
-
if not reminder_key or reminder_key not in REMINDERS:
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
# Find and modify the last tool result message
|
|
69
|
-
for msg in reversed(messages):
|
|
70
|
-
if msg.get('role') == 'tool':
|
|
71
|
-
msg['content'] = msg.get('content', '') + '\n\n' + REMINDERS[reminder_key]
|
|
72
|
-
break
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Export the plugin
|
|
76
|
-
reminder_plugin = [after_each_tool(inject_reminder_handler)]
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"""Shell Approval plugin - Asks user approval for shell commands."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
from connectonion.core.events import before_each_tool
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from connectonion.core.agent import Agent
|
|
9
|
-
|
|
10
|
-
SAFE_PATTERNS = [
|
|
11
|
-
r'^ls\b', r'^ll\b', r'^cat\b', r'^head\b', r'^tail\b', r'^less\b', r'^more\b',
|
|
12
|
-
r'^grep\b', r'^rg\b', r'^find\b', r'^fd\b', r'^which\b', r'^whereis\b',
|
|
13
|
-
r'^type\b', r'^file\b', r'^stat\b', r'^wc\b', r'^pwd\b', r'^echo\b',
|
|
14
|
-
r'^printf\b', r'^date\b', r'^whoami\b', r'^id\b', r'^env\b', r'^printenv\b',
|
|
15
|
-
r'^uname\b', r'^hostname\b', r'^df\b', r'^du\b', r'^free\b', r'^ps\b',
|
|
16
|
-
r'^top\b', r'^htop\b', r'^tree\b',
|
|
17
|
-
r'^git\s+status\b', r'^git\s+log\b', r'^git\s+diff\b', r'^git\s+show\b',
|
|
18
|
-
r'^git\s+branch\b', r'^git\s+remote\b', r'^git\s+tag\b',
|
|
19
|
-
r'^npm\s+list\b', r'^npm\s+ls\b', r'^pip\s+list\b', r'^pip\s+show\b',
|
|
20
|
-
r'^python\s+--version\b', r'^node\s+--version\b', r'^cargo\s+--version\b',
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _is_safe(command: str) -> bool:
|
|
25
|
-
cmd = command.strip()
|
|
26
|
-
return any(re.search(pattern, cmd) for pattern in SAFE_PATTERNS)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _check_approval(agent: 'Agent') -> None:
|
|
30
|
-
pending = agent.current_session.get('pending_tool') if agent.current_session else None
|
|
31
|
-
if not pending:
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
tool_name = pending.get('name', '')
|
|
35
|
-
if tool_name not in ('bash', 'shell', 'run', 'run_in_dir'):
|
|
36
|
-
return
|
|
37
|
-
|
|
38
|
-
args = pending.get('arguments', {})
|
|
39
|
-
command = args.get('command', '')
|
|
40
|
-
base_cmd = command.strip().split()[0] if command.strip() else ''
|
|
41
|
-
|
|
42
|
-
approved_cmds = agent.current_session.get('shell_approved_cmds', set()) if agent.current_session else set()
|
|
43
|
-
if base_cmd in approved_cmds:
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
if _is_safe(command):
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
from connectonion.cli.co_ai.tui.context import is_tui_active, show_choice_selector_sync, show_modal_sync
|
|
50
|
-
|
|
51
|
-
if is_tui_active():
|
|
52
|
-
from connectonion.cli.co_ai.tui.modals import TextInputModal
|
|
53
|
-
|
|
54
|
-
truncated = command[:60] + "..." if len(command) > 60 else command
|
|
55
|
-
question = f"Execute: `{truncated}`"
|
|
56
|
-
options = [
|
|
57
|
-
"Yes, execute",
|
|
58
|
-
f"Auto approve '{base_cmd}' for this session",
|
|
59
|
-
"No, tell agent what I want",
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
choice = show_choice_selector_sync(question, options, allow_other=False)
|
|
63
|
-
|
|
64
|
-
if choice == options[0]:
|
|
65
|
-
return
|
|
66
|
-
elif choice == options[1]:
|
|
67
|
-
if agent.current_session is not None:
|
|
68
|
-
if 'shell_approved_cmds' not in agent.current_session:
|
|
69
|
-
agent.current_session['shell_approved_cmds'] = set()
|
|
70
|
-
agent.current_session['shell_approved_cmds'].add(base_cmd)
|
|
71
|
-
return
|
|
72
|
-
else:
|
|
73
|
-
feedback = show_modal_sync(TextInputModal("What do you want instead?"))
|
|
74
|
-
raise ValueError(f"User feedback: {feedback}")
|
|
75
|
-
else:
|
|
76
|
-
from rich.console import Console
|
|
77
|
-
from rich.panel import Panel
|
|
78
|
-
from rich.syntax import Syntax
|
|
79
|
-
from connectonion.tui import pick
|
|
80
|
-
|
|
81
|
-
console = Console()
|
|
82
|
-
console.print()
|
|
83
|
-
syntax = Syntax(command, "bash", theme="monokai", word_wrap=True)
|
|
84
|
-
console.print(Panel(syntax, title="[yellow]Shell Command[/yellow]", border_style="yellow"))
|
|
85
|
-
|
|
86
|
-
choice = pick("Execute this command?", [
|
|
87
|
-
"Yes, execute",
|
|
88
|
-
f"Auto approve '{base_cmd}' in this session",
|
|
89
|
-
"No, tell agent what I want"
|
|
90
|
-
], console=console)
|
|
91
|
-
|
|
92
|
-
if choice == "Yes, execute":
|
|
93
|
-
return
|
|
94
|
-
elif choice.startswith("Auto approve"):
|
|
95
|
-
if agent.current_session is not None:
|
|
96
|
-
if 'shell_approved_cmds' not in agent.current_session:
|
|
97
|
-
agent.current_session['shell_approved_cmds'] = set()
|
|
98
|
-
agent.current_session['shell_approved_cmds'].add(base_cmd)
|
|
99
|
-
return
|
|
100
|
-
else:
|
|
101
|
-
feedback = input("What do you want the agent to do instead? ")
|
|
102
|
-
raise ValueError(f"User feedback: {feedback}")
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
shell_approval = [before_each_tool(_check_approval)]
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools, or otherwise make any changes to the system. This supersedes any other instructions you have received.
|
|
2
|
-
|
|
3
|
-
## Plan File Info
|
|
4
|
-
${PLAN_EXISTS ? "A plan file already exists at ${PLAN_FILE_PATH}. You can read it and make incremental edits." : "No plan file exists yet. You should create your plan at ${PLAN_FILE_PATH}."}
|
|
5
|
-
|
|
6
|
-
You should build your plan incrementally by writing to or editing this file. This is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
|
|
7
|
-
|
|
8
|
-
## Plan Workflow
|
|
9
|
-
|
|
10
|
-
### Phase 1: Initial Understanding
|
|
11
|
-
Goal: Understand the user's request by reading through code and asking questions.
|
|
12
|
-
|
|
13
|
-
1. Focus on understanding the user's request and the code associated with it
|
|
14
|
-
2. Use exploration tools to understand the codebase structure
|
|
15
|
-
3. Ask clarifying questions to resolve ambiguities
|
|
16
|
-
|
|
17
|
-
### Phase 2: Design
|
|
18
|
-
Goal: Design an implementation approach based on your exploration.
|
|
19
|
-
|
|
20
|
-
1. Consider different approaches and their trade-offs
|
|
21
|
-
2. Identify the files that need to be modified
|
|
22
|
-
3. Plan the order of changes
|
|
23
|
-
|
|
24
|
-
### Phase 3: Final Plan
|
|
25
|
-
Goal: Write your final plan to the plan file.
|
|
26
|
-
|
|
27
|
-
- Include only your recommended approach, not all alternatives
|
|
28
|
-
- Be concise enough to scan quickly, but detailed enough to execute
|
|
29
|
-
- Include the paths of critical files to be modified
|
|
30
|
-
|
|
31
|
-
### Phase 4: Exit Plan Mode
|
|
32
|
-
Once you are happy with your final plan, call the exit_plan_mode tool to indicate you are done planning.
|
|
33
|
-
|
|
34
|
-
NOTE: Feel free to ask the user questions at any point. Don't make large assumptions about user intent.
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"""System reminders for contextual guidance.
|
|
2
|
-
|
|
3
|
-
System reminders are automatically injected into tool results or conversation
|
|
4
|
-
to provide contextual constraints and guidance. They override default behavior
|
|
5
|
-
when applicable.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
from connectonion.cli.co_ai.reminders import inject_reminder, REMINDERS
|
|
9
|
-
|
|
10
|
-
# Inject a specific reminder
|
|
11
|
-
result = inject_reminder(tool_result, "plan_mode_active")
|
|
12
|
-
|
|
13
|
-
# Check if reminder should be shown
|
|
14
|
-
if should_show_todo_reminder(agent):
|
|
15
|
-
result = inject_reminder(result, "todo_reminder")
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from typing import Optional, Dict, Any
|
|
19
|
-
from functools import wraps
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# System reminder templates
|
|
23
|
-
REMINDERS: Dict[str, str] = {
|
|
24
|
-
# Plan mode reminder - injected when plan mode is active
|
|
25
|
-
"plan_mode_active": """<system-reminder>
|
|
26
|
-
Plan mode is active. You are in READ-ONLY exploration mode.
|
|
27
|
-
- You can ONLY use: glob, grep, read_file to explore
|
|
28
|
-
- You can ONLY write to the plan file (.co/PLAN.md)
|
|
29
|
-
- Do NOT make any code changes until plan is approved
|
|
30
|
-
- Use write_plan() to update your plan
|
|
31
|
-
- Use exit_plan_mode() when plan is complete
|
|
32
|
-
</system-reminder>""",
|
|
33
|
-
|
|
34
|
-
# Todo reminder - gentle nudge to use todo tracking
|
|
35
|
-
"todo_reminder": """<system-reminder>
|
|
36
|
-
Consider using todo_list() to track progress for multi-step tasks.
|
|
37
|
-
Mark tasks complete immediately when finished.
|
|
38
|
-
</system-reminder>""",
|
|
39
|
-
|
|
40
|
-
# Read-only mode for explore agent
|
|
41
|
-
"read_only": """<system-reminder>
|
|
42
|
-
This is READ-ONLY mode. You are PROHIBITED from modifying any files.
|
|
43
|
-
Only use: glob, grep, read_file, and read-only bash commands.
|
|
44
|
-
</system-reminder>""",
|
|
45
|
-
|
|
46
|
-
# Security reminder after reading sensitive files
|
|
47
|
-
"security": """<system-reminder>
|
|
48
|
-
This file may contain sensitive information (credentials, keys, tokens).
|
|
49
|
-
- Never expose secrets in output
|
|
50
|
-
- Never commit this file if it contains real credentials
|
|
51
|
-
- Consider using environment variables instead
|
|
52
|
-
</system-reminder>""",
|
|
53
|
-
|
|
54
|
-
# Anti-over-engineering reminder
|
|
55
|
-
"simplicity": """<system-reminder>
|
|
56
|
-
Keep it simple:
|
|
57
|
-
- Only change what's directly needed
|
|
58
|
-
- Don't add error handling for scenarios that can't happen
|
|
59
|
-
- Three similar lines > premature abstraction
|
|
60
|
-
- If unused, delete completely
|
|
61
|
-
</system-reminder>""",
|
|
62
|
-
|
|
63
|
-
# Workflow reminder - injected when writing Python files
|
|
64
|
-
"connectonion_workflow": """<system-reminder>
|
|
65
|
-
When creating agents, ALWAYS use ConnectOnion framework:
|
|
66
|
-
- `from connectonion import Agent`
|
|
67
|
-
- Atomic tool functions (one thing each)
|
|
68
|
-
- `agent = Agent("name", tools=[...])` + `agent.input("task")`
|
|
69
|
-
|
|
70
|
-
NEVER create standalone scripts with argparse. NEVER skip ask_user confirmation.
|
|
71
|
-
</system-reminder>""",
|
|
72
|
-
|
|
73
|
-
# After code write reminder
|
|
74
|
-
"after_write_code": """<system-reminder>
|
|
75
|
-
Code written. If this is an agent:
|
|
76
|
-
- Verify it uses `from connectonion import Agent`
|
|
77
|
-
- Verify tools are atomic functions
|
|
78
|
-
- If it's a standalone script with argparse, REWRITE using ConnectOnion
|
|
79
|
-
</system-reminder>""",
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def inject_reminder(content: str, reminder_key: str) -> str:
|
|
84
|
-
"""
|
|
85
|
-
Inject a system reminder into content.
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
content: The original content (tool result, message, etc.)
|
|
89
|
-
reminder_key: Key from REMINDERS dict
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
Content with reminder injected at the end
|
|
93
|
-
"""
|
|
94
|
-
if reminder_key not in REMINDERS:
|
|
95
|
-
return content
|
|
96
|
-
|
|
97
|
-
reminder = REMINDERS[reminder_key]
|
|
98
|
-
return f"{content}\n\n{reminder}"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def with_reminder(reminder_key: str):
|
|
102
|
-
"""
|
|
103
|
-
Decorator to inject a reminder into tool results.
|
|
104
|
-
|
|
105
|
-
Usage:
|
|
106
|
-
@with_reminder("plan_mode_active")
|
|
107
|
-
def some_tool(...):
|
|
108
|
-
return result
|
|
109
|
-
"""
|
|
110
|
-
def decorator(func):
|
|
111
|
-
@wraps(func)
|
|
112
|
-
def wrapper(*args, **kwargs):
|
|
113
|
-
result = func(*args, **kwargs)
|
|
114
|
-
if isinstance(result, str):
|
|
115
|
-
return inject_reminder(result, reminder_key)
|
|
116
|
-
return result
|
|
117
|
-
return wrapper
|
|
118
|
-
return decorator
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def should_show_security_reminder(file_path: str) -> bool:
|
|
122
|
-
"""Check if file path suggests sensitive content."""
|
|
123
|
-
sensitive_patterns = [
|
|
124
|
-
".env",
|
|
125
|
-
"credentials",
|
|
126
|
-
"secrets",
|
|
127
|
-
"config/prod",
|
|
128
|
-
"keys",
|
|
129
|
-
"password",
|
|
130
|
-
"token",
|
|
131
|
-
".pem",
|
|
132
|
-
".key",
|
|
133
|
-
]
|
|
134
|
-
path_lower = file_path.lower()
|
|
135
|
-
return any(pattern in path_lower for pattern in sensitive_patterns)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def get_contextual_reminders(context: Dict[str, Any]) -> list:
|
|
139
|
-
"""
|
|
140
|
-
Get list of reminders based on current context.
|
|
141
|
-
|
|
142
|
-
Args:
|
|
143
|
-
context: Dict with current state info:
|
|
144
|
-
- plan_mode: bool
|
|
145
|
-
- todo_count: int
|
|
146
|
-
- file_path: str (for security check)
|
|
147
|
-
|
|
148
|
-
Returns:
|
|
149
|
-
List of reminder keys that should be shown
|
|
150
|
-
"""
|
|
151
|
-
reminders = []
|
|
152
|
-
|
|
153
|
-
if context.get("plan_mode"):
|
|
154
|
-
reminders.append("plan_mode_active")
|
|
155
|
-
|
|
156
|
-
if context.get("file_path") and should_show_security_reminder(context["file_path"]):
|
|
157
|
-
reminders.append("security")
|
|
158
|
-
|
|
159
|
-
return reminders
|
|
File without changes
|
|
File without changes
|