connectonion 0.5.8__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.
Files changed (113) hide show
  1. connectonion/__init__.py +78 -0
  2. connectonion/address.py +320 -0
  3. connectonion/agent.py +450 -0
  4. connectonion/announce.py +84 -0
  5. connectonion/asgi.py +287 -0
  6. connectonion/auto_debug_exception.py +181 -0
  7. connectonion/cli/__init__.py +3 -0
  8. connectonion/cli/browser_agent/__init__.py +5 -0
  9. connectonion/cli/browser_agent/browser.py +243 -0
  10. connectonion/cli/browser_agent/prompt.md +107 -0
  11. connectonion/cli/commands/__init__.py +1 -0
  12. connectonion/cli/commands/auth_commands.py +527 -0
  13. connectonion/cli/commands/browser_commands.py +27 -0
  14. connectonion/cli/commands/create.py +511 -0
  15. connectonion/cli/commands/deploy_commands.py +220 -0
  16. connectonion/cli/commands/doctor_commands.py +173 -0
  17. connectonion/cli/commands/init.py +469 -0
  18. connectonion/cli/commands/project_cmd_lib.py +828 -0
  19. connectonion/cli/commands/reset_commands.py +149 -0
  20. connectonion/cli/commands/status_commands.py +168 -0
  21. connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
  22. connectonion/cli/docs/connectonion.md +1256 -0
  23. connectonion/cli/docs.md +123 -0
  24. connectonion/cli/main.py +148 -0
  25. connectonion/cli/templates/meta-agent/README.md +287 -0
  26. connectonion/cli/templates/meta-agent/agent.py +196 -0
  27. connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
  28. connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
  29. connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
  30. connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
  31. connectonion/cli/templates/minimal/README.md +56 -0
  32. connectonion/cli/templates/minimal/agent.py +40 -0
  33. connectonion/cli/templates/playwright/README.md +118 -0
  34. connectonion/cli/templates/playwright/agent.py +336 -0
  35. connectonion/cli/templates/playwright/prompt.md +102 -0
  36. connectonion/cli/templates/playwright/requirements.txt +3 -0
  37. connectonion/cli/templates/web-research/agent.py +122 -0
  38. connectonion/connect.py +128 -0
  39. connectonion/console.py +539 -0
  40. connectonion/debug_agent/__init__.py +13 -0
  41. connectonion/debug_agent/agent.py +45 -0
  42. connectonion/debug_agent/prompts/debug_assistant.md +72 -0
  43. connectonion/debug_agent/runtime_inspector.py +406 -0
  44. connectonion/debug_explainer/__init__.py +10 -0
  45. connectonion/debug_explainer/explain_agent.py +114 -0
  46. connectonion/debug_explainer/explain_context.py +263 -0
  47. connectonion/debug_explainer/explainer_prompt.md +29 -0
  48. connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
  49. connectonion/debugger_ui.py +1039 -0
  50. connectonion/decorators.py +208 -0
  51. connectonion/events.py +248 -0
  52. connectonion/execution_analyzer/__init__.py +9 -0
  53. connectonion/execution_analyzer/execution_analysis.py +93 -0
  54. connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
  55. connectonion/host.py +579 -0
  56. connectonion/interactive_debugger.py +342 -0
  57. connectonion/llm.py +801 -0
  58. connectonion/llm_do.py +307 -0
  59. connectonion/logger.py +300 -0
  60. connectonion/prompt_files/__init__.py +1 -0
  61. connectonion/prompt_files/analyze_contact.md +62 -0
  62. connectonion/prompt_files/eval_expected.md +12 -0
  63. connectonion/prompt_files/react_evaluate.md +11 -0
  64. connectonion/prompt_files/react_plan.md +16 -0
  65. connectonion/prompt_files/reflect.md +22 -0
  66. connectonion/prompts.py +144 -0
  67. connectonion/relay.py +200 -0
  68. connectonion/static/docs.html +688 -0
  69. connectonion/tool_executor.py +279 -0
  70. connectonion/tool_factory.py +186 -0
  71. connectonion/tool_registry.py +105 -0
  72. connectonion/trust.py +166 -0
  73. connectonion/trust_agents.py +71 -0
  74. connectonion/trust_functions.py +88 -0
  75. connectonion/tui/__init__.py +57 -0
  76. connectonion/tui/divider.py +39 -0
  77. connectonion/tui/dropdown.py +251 -0
  78. connectonion/tui/footer.py +31 -0
  79. connectonion/tui/fuzzy.py +56 -0
  80. connectonion/tui/input.py +278 -0
  81. connectonion/tui/keys.py +35 -0
  82. connectonion/tui/pick.py +130 -0
  83. connectonion/tui/providers.py +155 -0
  84. connectonion/tui/status_bar.py +163 -0
  85. connectonion/usage.py +161 -0
  86. connectonion/useful_events_handlers/__init__.py +16 -0
  87. connectonion/useful_events_handlers/reflect.py +116 -0
  88. connectonion/useful_plugins/__init__.py +20 -0
  89. connectonion/useful_plugins/calendar_plugin.py +163 -0
  90. connectonion/useful_plugins/eval.py +139 -0
  91. connectonion/useful_plugins/gmail_plugin.py +162 -0
  92. connectonion/useful_plugins/image_result_formatter.py +127 -0
  93. connectonion/useful_plugins/re_act.py +78 -0
  94. connectonion/useful_plugins/shell_approval.py +159 -0
  95. connectonion/useful_tools/__init__.py +44 -0
  96. connectonion/useful_tools/diff_writer.py +192 -0
  97. connectonion/useful_tools/get_emails.py +183 -0
  98. connectonion/useful_tools/gmail.py +1596 -0
  99. connectonion/useful_tools/google_calendar.py +613 -0
  100. connectonion/useful_tools/memory.py +380 -0
  101. connectonion/useful_tools/microsoft_calendar.py +604 -0
  102. connectonion/useful_tools/outlook.py +488 -0
  103. connectonion/useful_tools/send_email.py +205 -0
  104. connectonion/useful_tools/shell.py +97 -0
  105. connectonion/useful_tools/slash_command.py +201 -0
  106. connectonion/useful_tools/terminal.py +285 -0
  107. connectonion/useful_tools/todo_list.py +241 -0
  108. connectonion/useful_tools/web_fetch.py +216 -0
  109. connectonion/xray.py +467 -0
  110. connectonion-0.5.8.dist-info/METADATA +741 -0
  111. connectonion-0.5.8.dist-info/RECORD +113 -0
  112. connectonion-0.5.8.dist-info/WHEEL +4 -0
  113. connectonion-0.5.8.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,11 @@
1
+ You are evaluating whether a task is complete.
2
+
3
+ Given the user's original request and what was done, determine:
4
+ 1. Is the task truly complete?
5
+ 2. If not, what's still missing?
6
+
7
+ Be honest and critical. Don't say "complete" if important parts are missing.
8
+
9
+ Respond in ONE sentence:
10
+ - If complete: "Task complete: [brief summary of what was achieved]"
11
+ - If incomplete: "Not done yet: [what's still needed]"
@@ -0,0 +1,16 @@
1
+ # ReAct Planning
2
+
3
+ You are a planning assistant. When a user gives a task, think about it briefly.
4
+
5
+ ## Guidelines
6
+
7
+ - Keep it SHORT: 1-2 sentences max
8
+ - Focus on: What needs to be done and which tools to use
9
+ - Be practical and actionable
10
+
11
+ ## Examples
12
+
13
+ - "Need to check emails first, then reply to the urgent ones."
14
+ - "Will search for the file, then read its contents."
15
+ - "Should fetch the API data and analyze the response."
16
+ - "Let me check the database for user info, then format the report."
@@ -0,0 +1,22 @@
1
+ # Reflect
2
+
3
+ You are a reflection assistant. After each tool execution, provide a brief one-sentence reflection.
4
+
5
+ ## Guidelines
6
+
7
+ - Keep it SHORT: One sentence only
8
+ - Focus on: What happened + What's next
9
+ - Be practical and actionable
10
+ - No verbose explanations
11
+
12
+ ## Examples
13
+
14
+ Success cases:
15
+ - "Got 5 emails, let's check the first one for details."
16
+ - "Contact updated, now searching for related conversations."
17
+ - "File created successfully, running tests next."
18
+
19
+ Error cases:
20
+ - "Permission denied, need to check file ownership."
21
+ - "API rate limited, waiting before retry."
22
+ - "Invalid email format, asking user for correct address."
@@ -0,0 +1,144 @@
1
+ """
2
+ Purpose: Load and validate system prompts from files or strings with intelligent path detection
3
+ LLM-Note:
4
+ Dependencies: imports from [os, warnings, pathlib, typing] | imported by [agent.py] | no dedicated tests found
5
+ Data flow: receives system_prompt: Union[str, Path, None] from Agent.__init__ → checks if None (returns DEFAULT_PROMPT) → checks if Path object (reads file) → checks if str exists as file (reads) → warns if looks like file but doesn't exist → returns literal string
6
+ State/Effects: reads text files if path provided | emits UserWarning if path looks like file but doesn't exist | no writes or global state
7
+ Integration: exposes load_system_prompt(prompt), DEFAULT_PROMPT constant | used by Agent to load system prompts from various sources | supports .md, .txt, .prompt file extensions | Path objects enforce file must exist
8
+ Performance: file I/O only when path provided | heuristic checks (file extension, path separators) are fast string operations
9
+ Errors: raises FileNotFoundError if Path doesn't exist | raises ValueError if Path is not a file or file is empty | raises ValueError if file not UTF-8 | warns (doesn't fail) for str that looks like missing file
10
+ """
11
+
12
+ import os
13
+ import warnings
14
+ from pathlib import Path
15
+ from typing import Union
16
+
17
+
18
+ DEFAULT_PROMPT = "You are a helpful assistant that can use tools to complete tasks."
19
+
20
+
21
+ def _looks_like_file_path(text: str) -> bool:
22
+ """Check if a string looks like a file path rather than prompt text."""
23
+ # Check for file extensions
24
+ has_file_extension = '.' in text and text.split('.')[-1] in ['md', 'txt', 'prompt']
25
+ # Check for path separators
26
+ has_path_separator = '/' in text or '\\' in text
27
+
28
+ return has_file_extension or has_path_separator
29
+
30
+
31
+ def _warn_if_missing_file(prompt: str) -> None:
32
+ """Warn user if prompt looks like a file path but doesn't exist."""
33
+ if _looks_like_file_path(prompt) and not os.path.exists(prompt):
34
+ abs_path = os.path.abspath(prompt)
35
+ cwd = os.getcwd()
36
+
37
+ # Suggest better approach
38
+ suggestion = ""
39
+ if '/' in prompt or '\\' in prompt:
40
+ # Has path separators, suggest Path object
41
+ suggestion = f"\n Tip: Use Path object for explicit file loading: Path('{prompt}')"
42
+ else:
43
+ # Just filename, suggest correct directory or Path
44
+ suggestion = (f"\n Tip: Either run from the correct directory, or use absolute path:\n"
45
+ f" Path(__file__).parent / '{prompt}'")
46
+
47
+ warnings.warn(
48
+ f"'{prompt}' looks like a file path but doesn't exist.\n"
49
+ f" Looked in: {abs_path}\n"
50
+ f" Current directory: {cwd}\n"
51
+ f" Treating as literal prompt text."
52
+ f"{suggestion}",
53
+ UserWarning,
54
+ stacklevel=3
55
+ )
56
+
57
+
58
+ def load_system_prompt(prompt: Union[str, Path, None]) -> str:
59
+ """
60
+ Load system prompt from various sources.
61
+
62
+ Args:
63
+ prompt: Can be:
64
+ - None: Returns default prompt
65
+ - str: Either a file path (if file exists) or literal prompt text
66
+ - Path: Path object to a text file
67
+
68
+ Returns:
69
+ str: The loaded system prompt
70
+
71
+ Examples:
72
+ >>> load_system_prompt(None)
73
+ 'You are a helpful assistant that can use tools to complete tasks.'
74
+
75
+ >>> load_system_prompt("You are a helpful assistant")
76
+ 'You are a helpful assistant'
77
+
78
+ >>> load_system_prompt("prompts/assistant.md") # If file exists
79
+ # Returns content from the file
80
+
81
+ >>> load_system_prompt(Path("prompts/assistant")) # Any text file
82
+ # Returns content from the file
83
+
84
+ Raises:
85
+ FileNotFoundError: If a Path object points to non-existent file
86
+ ValueError: If file is empty or not valid UTF-8 text
87
+ """
88
+ if prompt is None:
89
+ return DEFAULT_PROMPT
90
+
91
+ if isinstance(prompt, Path):
92
+ # Explicit Path object - must exist
93
+ if not prompt.exists():
94
+ raise FileNotFoundError(f"Prompt file not found: {prompt}")
95
+ if not prompt.is_file():
96
+ raise ValueError(f"Path is not a file: {prompt}")
97
+ return _read_text_file(prompt)
98
+
99
+ if isinstance(prompt, str):
100
+ # Check if it's an existing file
101
+ if os.path.exists(prompt) and os.path.isfile(prompt):
102
+ return _read_text_file(Path(prompt))
103
+
104
+ # Warn if it looks like a missing file
105
+ _warn_if_missing_file(prompt)
106
+
107
+ # Treat as literal prompt text
108
+ return prompt
109
+
110
+ raise TypeError(f"Invalid prompt type: {type(prompt).__name__}. Expected str, Path, or None.")
111
+
112
+
113
+ def _read_text_file(path: Path) -> str:
114
+ """
115
+ Read content from a text file.
116
+
117
+ Args:
118
+ path: Path to the text file
119
+
120
+ Returns:
121
+ str: File content
122
+
123
+ Raises:
124
+ ValueError: If file is empty or not valid UTF-8
125
+ """
126
+ try:
127
+ content = path.read_text(encoding='utf-8').strip()
128
+ if not content:
129
+ raise ValueError(f"Prompt file '{path}' is empty. Please add content or use a different file.")
130
+ return content
131
+ except UnicodeDecodeError:
132
+ raise ValueError(
133
+ f"File '{path}' is not a valid UTF-8 text file. "
134
+ f"System prompts must be text files."
135
+ )
136
+ except ValueError:
137
+ # Re-raise ValueError (empty file)
138
+ raise
139
+ except PermissionError:
140
+ # Re-raise PermissionError
141
+ raise
142
+ except Exception as e:
143
+ # Catch any other unexpected errors
144
+ raise RuntimeError(f"Error reading prompt file '{path}': {e}")
connectonion/relay.py ADDED
@@ -0,0 +1,200 @@
1
+ """
2
+ Purpose: WebSocket relay client for agent-to-agent communication via central relay server using INPUT/OUTPUT protocol
3
+ LLM-Note:
4
+ Dependencies: imports from [json, asyncio, typing, websockets] | imported by [host.py] | tested by [tests/test_relay.py]
5
+ Data flow: host() with relay_url → connect(relay_url) → WebSocket established → send_announce(ws, announce_msg) → serve_loop() → wait_for_task(ws) receives INPUT message from relay → task_handler(prompt) executes → send OUTPUT response via WebSocket → heartbeat re-announces every 60s
6
+ State/Effects: maintains WebSocket connection to relay | reads incoming JSON messages (INPUT type) | writes outgoing JSON messages (OUTPUT type) | prints status to stdout | asyncio timeout for heartbeat | no file I/O
7
+ Integration: exposes connect(relay_url), send_announce(ws, msg), wait_for_task(ws, timeout), send_response(ws, input_id, result), serve_loop(ws, announce_msg, task_handler, heartbeat_interval) | used by host() with relay_url to make agent discoverable on relay network | task_handler is async function (prompt: str) -> str | Protocol: INPUT/OUTPUT messages (not TASK/RESPONSE)
8
+ Performance: async/await non-blocking I/O | heartbeat_interval=60s default (configurable) | timeout-based heartbeat scheduling | WebSocket maintains persistent connection
9
+ Errors: let it crash - ImportError if websockets missing | asyncio.TimeoutError used for heartbeat timing | websockets.ConnectionClosed exits serve loop gracefully
10
+
11
+ WebSocket relay client functions.
12
+
13
+ Simple async functions for connecting to relay and exchanging messages using INPUT/OUTPUT protocol.
14
+ No classes needed - just stateless functions operating on websocket connections.
15
+ """
16
+
17
+ import json
18
+ import asyncio
19
+ from typing import Dict, Any
20
+ import websockets
21
+
22
+
23
+ async def connect(relay_url: str = "wss://oo.openonion.ai/ws/announce"):
24
+ """
25
+ Connect to relay WebSocket endpoint.
26
+
27
+ Args:
28
+ relay_url: WebSocket URL for relay (default: production relay)
29
+
30
+ Returns:
31
+ WebSocket connection object
32
+
33
+ Example:
34
+ >>> ws = await connect()
35
+ >>> # Now use ws for sending/receiving
36
+ """
37
+ return await websockets.connect(relay_url)
38
+
39
+
40
+ async def send_announce(websocket, announce_message: Dict[str, Any]):
41
+ """
42
+ Send ANNOUNCE message through WebSocket.
43
+
44
+ Args:
45
+ websocket: WebSocket connection from connect()
46
+ announce_message: Dict from create_announce_message()
47
+
48
+ Note:
49
+ Server responds with error message only if something went wrong.
50
+ No response = success (per protocol spec)
51
+
52
+ Example:
53
+ >>> from . import announce, address
54
+ >>> addr = address.load()
55
+ >>> msg = announce.create_announce_message(addr, "My agent", [])
56
+ >>> await send_announce(ws, msg)
57
+ """
58
+ message_json = json.dumps(announce_message)
59
+ await websocket.send(message_json)
60
+
61
+
62
+ async def wait_for_task(websocket, timeout: float = None) -> Dict[str, Any]:
63
+ """
64
+ Wait for next INPUT message from relay.
65
+
66
+ Args:
67
+ websocket: WebSocket connection from connect()
68
+ timeout: Optional timeout in seconds (None = wait forever)
69
+
70
+ Returns:
71
+ INPUT message dict:
72
+ {
73
+ "type": "INPUT",
74
+ "input_id": "abc123...",
75
+ "prompt": "Translate hello to Spanish",
76
+ "from_address": "0x..."
77
+ }
78
+
79
+ Raises:
80
+ asyncio.TimeoutError: If timeout expires
81
+ websockets.exceptions.ConnectionClosed: If connection lost
82
+
83
+ Example:
84
+ >>> task = await wait_for_task(ws)
85
+ >>> print(task["prompt"])
86
+ Translate hello to Spanish
87
+ """
88
+ if timeout:
89
+ data = await asyncio.wait_for(websocket.recv(), timeout=timeout)
90
+ else:
91
+ data = await websocket.recv()
92
+
93
+ message = json.loads(data)
94
+ return message
95
+
96
+
97
+ async def send_response(
98
+ websocket,
99
+ input_id: str,
100
+ result: str,
101
+ success: bool = True
102
+ ):
103
+ """
104
+ Send output response back to relay.
105
+
106
+ Args:
107
+ websocket: WebSocket connection from connect()
108
+ input_id: ID from INPUT message
109
+ result: Agent's response/output
110
+ success: Whether task succeeded (default True)
111
+
112
+ Example:
113
+ >>> task = await wait_for_task(ws)
114
+ >>> result = agent.input(task["prompt"])
115
+ >>> await send_response(ws, task["input_id"], result)
116
+ """
117
+ response_message = {
118
+ "type": "OUTPUT",
119
+ "input_id": input_id,
120
+ "result": result,
121
+ "success": success
122
+ }
123
+
124
+ message_json = json.dumps(response_message)
125
+ await websocket.send(message_json)
126
+
127
+
128
+ async def serve_loop(
129
+ websocket,
130
+ announce_message: Dict[str, Any],
131
+ task_handler,
132
+ heartbeat_interval: int = 60
133
+ ):
134
+ """
135
+ Main serving loop for agent.
136
+
137
+ This handles:
138
+ - Initial ANNOUNCE
139
+ - Periodic heartbeat ANNOUNCE (every 60s)
140
+ - Receiving and processing TASK messages
141
+ - Sending responses
142
+
143
+ Args:
144
+ websocket: WebSocket connection from connect()
145
+ announce_message: ANNOUNCE message dict (will be re-sent for heartbeat)
146
+ task_handler: Async function that takes (prompt: str) -> str
147
+ heartbeat_interval: Seconds between heartbeat ANNOUNCEs (default 60)
148
+
149
+ Example:
150
+ >>> async def handler(prompt):
151
+ ... return agent.input(prompt)
152
+ >>> await serve_loop(ws, announce_msg, handler)
153
+ """
154
+ # Send initial ANNOUNCE
155
+ await send_announce(websocket, announce_message)
156
+ print(f"✓ Announced to relay: {announce_message['address'][:12]}...")
157
+
158
+ # Track last heartbeat time
159
+ last_heartbeat = asyncio.get_event_loop().time()
160
+
161
+ # Main loop
162
+ while True:
163
+ try:
164
+ # Wait for message with timeout to allow heartbeat
165
+ task = await wait_for_task(websocket, timeout=heartbeat_interval)
166
+
167
+ # Handle INPUT message
168
+ if task.get("type") == "INPUT":
169
+ print(f"→ Received input: {task['input_id'][:8]}...")
170
+
171
+ # Process with handler
172
+ result = await task_handler(task["prompt"])
173
+
174
+ # Send OUTPUT response
175
+ output_message = {
176
+ "type": "OUTPUT",
177
+ "input_id": task["input_id"],
178
+ "result": result
179
+ }
180
+ await websocket.send(json.dumps(output_message))
181
+ print(f"✓ Sent output: {task['input_id'][:8]}...")
182
+
183
+ elif task.get("type") == "ERROR":
184
+ print(f"✗ Error from relay: {task.get('error')}")
185
+
186
+ except asyncio.TimeoutError:
187
+ # Time for heartbeat ANNOUNCE
188
+ # Update timestamp in message
189
+ announce_message["timestamp"] = int(asyncio.get_event_loop().time())
190
+
191
+ # Need to re-sign with new timestamp
192
+ # For now, just send without updating signature
193
+ # TODO: Re-sign message with new timestamp
194
+ await send_announce(websocket, announce_message)
195
+ print("♥ Sent heartbeat")
196
+ last_heartbeat = asyncio.get_event_loop().time()
197
+
198
+ except websockets.exceptions.ConnectionClosed:
199
+ print("✗ Connection to relay closed")
200
+ break