connectonion 0.5.5__tar.gz → 0.5.7__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.
- {connectonion-0.5.5 → connectonion-0.5.7}/PKG-INFO +1 -1
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/__init__.py +1 -1
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/asgi.py +51 -2
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/deploy_commands.py +110 -16
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/host.py +84 -70
- {connectonion-0.5.5 → connectonion-0.5.7}/pyproject.toml +1 -1
- {connectonion-0.5.5 → connectonion-0.5.7}/.gitignore +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/address.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/agent.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/announce.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/auto_debug_exception.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/browser_agent/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/browser_agent/browser.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/browser_agent/prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/auth_commands.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/browser_commands.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/create.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/doctor_commands.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/init.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/project_cmd_lib.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/reset_commands.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/commands/status_commands.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/docs/connectonion.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/docs.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/main.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/meta-agent/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/meta-agent/agent.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/meta-agent/prompts/metagent.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/meta-agent/prompts/think_prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/minimal/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/minimal/agent.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/playwright/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/playwright/agent.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/playwright/prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/playwright/requirements.txt +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/web-research/agent.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/connect.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/console.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_agent/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_agent/agent.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_agent/prompts/debug_assistant.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_agent/runtime_inspector.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_explainer/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_explainer/explain_agent.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_explainer/explain_context.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_explainer/explainer_prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_explainer/root_cause_analysis_prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debugger_ui.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/decorators.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/events.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/execution_analyzer/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/execution_analyzer/execution_analysis.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/execution_analyzer/execution_analysis_prompt.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/interactive_debugger.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/llm.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/llm_do.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/logger.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/prompt_files/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/prompt_files/analyze_contact.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/prompt_files/eval_expected.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/prompt_files/react_evaluate.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/prompt_files/react_plan.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/prompt_files/reflect.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/prompts.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/relay.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/static/docs.html +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tool_executor.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tool_factory.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tool_registry.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/trust.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/trust_agents.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/trust_functions.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/divider.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/dropdown.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/footer.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/fuzzy.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/input.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/keys.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/pick.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/providers.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/tui/status_bar.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/usage.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_events_handlers/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_events_handlers/reflect.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/calendar_plugin.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/eval.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/gmail_plugin.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/image_result_formatter.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/re_act.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/shell_approval.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/__init__.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/diff_writer.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/get_emails.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/gmail.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/google_calendar.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/memory.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/microsoft_calendar.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/outlook.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/send_email.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/shell.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/slash_command.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/terminal.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/todo_list.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_tools/web_fetch.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/connectonion/xray.py +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/cli/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/debug/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/integrations/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/network/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/templates/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/tui/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/useful_plugins/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/docs/useful_tools/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/examples/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/examples/browser-agent/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/examples/email-agent/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/examples/simple-agent/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/prompts/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/prompts/formats/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/tests/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/tests/cli/README.md +0 -0
- {connectonion-0.5.5 → connectonion-0.5.7}/tests/cli/aws/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: connectonion
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
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
|
|
@@ -6,7 +6,9 @@ requests. Separated from host.py for better testing and smaller file size.
|
|
|
6
6
|
Design decision: Raw ASGI instead of Starlette/FastAPI for full protocol control.
|
|
7
7
|
See: docs/design-decisions/022-raw-asgi-implementation.md
|
|
8
8
|
"""
|
|
9
|
+
import hmac
|
|
9
10
|
import json
|
|
11
|
+
import os
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
|
|
12
14
|
from pydantic import BaseModel
|
|
@@ -34,11 +36,20 @@ async def read_body(receive) -> bytes:
|
|
|
34
36
|
return body
|
|
35
37
|
|
|
36
38
|
|
|
39
|
+
# CORS headers for cross-origin requests (e.g., frontend at o.openonion.ai
|
|
40
|
+
# calling deployed agents at *.agents.openonion.ai)
|
|
41
|
+
CORS_HEADERS = [
|
|
42
|
+
[b"access-control-allow-origin", b"*"],
|
|
43
|
+
[b"access-control-allow-methods", b"GET, POST, OPTIONS"],
|
|
44
|
+
[b"access-control-allow-headers", b"authorization, content-type"],
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
37
48
|
async def send_json(send, data: dict, status: int = 200):
|
|
38
49
|
"""Send JSON response via ASGI send."""
|
|
39
50
|
body = json.dumps(data, default=_json_default).encode()
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
headers = [[b"content-type", b"application/json"]] + CORS_HEADERS
|
|
52
|
+
await send({"type": "http.response.start", "status": status, "headers": headers})
|
|
42
53
|
await send({"type": "http.response.body", "body": body})
|
|
43
54
|
|
|
44
55
|
|
|
@@ -52,6 +63,13 @@ async def send_html(send, html: bytes, status: int = 200):
|
|
|
52
63
|
await send({"type": "http.response.body", "body": html})
|
|
53
64
|
|
|
54
65
|
|
|
66
|
+
async def send_text(send, text: str, status: int = 200):
|
|
67
|
+
"""Send plain text response via ASGI send."""
|
|
68
|
+
headers = [[b"content-type", b"text/plain; charset=utf-8"]] + CORS_HEADERS
|
|
69
|
+
await send({"type": "http.response.start", "status": status, "headers": headers})
|
|
70
|
+
await send({"type": "http.response.body", "body": text.encode()})
|
|
71
|
+
|
|
72
|
+
|
|
55
73
|
async def handle_http(
|
|
56
74
|
scope,
|
|
57
75
|
receive,
|
|
@@ -81,6 +99,37 @@ async def handle_http(
|
|
|
81
99
|
"""
|
|
82
100
|
method, path = scope["method"], scope["path"]
|
|
83
101
|
|
|
102
|
+
# Handle CORS preflight requests
|
|
103
|
+
if method == "OPTIONS":
|
|
104
|
+
headers = CORS_HEADERS + [[b"content-length", b"0"]]
|
|
105
|
+
await send({"type": "http.response.start", "status": 204, "headers": headers})
|
|
106
|
+
await send({"type": "http.response.body", "body": b""})
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# Admin endpoints require API key auth
|
|
110
|
+
if path.startswith("/admin"):
|
|
111
|
+
headers = dict(scope.get("headers", []))
|
|
112
|
+
auth = headers.get(b"authorization", b"").decode()
|
|
113
|
+
expected = os.environ.get("OPENONION_API_KEY", "")
|
|
114
|
+
if not expected or not auth.startswith("Bearer ") or not hmac.compare_digest(auth[7:], expected):
|
|
115
|
+
await send_json(send, {"error": "unauthorized"}, 401)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
if method == "GET" and path == "/admin/logs":
|
|
119
|
+
result = handlers["admin_logs"]()
|
|
120
|
+
if "error" in result:
|
|
121
|
+
await send_json(send, result, 404)
|
|
122
|
+
else:
|
|
123
|
+
await send_text(send, result["content"])
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
if method == "GET" and path == "/admin/sessions":
|
|
127
|
+
await send_json(send, handlers["admin_sessions"]())
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
await send_json(send, {"error": "not found"}, 404)
|
|
131
|
+
return
|
|
132
|
+
|
|
84
133
|
if method == "POST" and path == "/input":
|
|
85
134
|
body = await read_body(receive)
|
|
86
135
|
try:
|
|
@@ -5,11 +5,13 @@ LLM-Note:
|
|
|
5
5
|
Data flow: handle_deploy() → validates git repo and .co/config.toml → _get_api_key() loads OPENONION_API_KEY → reads config.toml for project name and secrets path → dotenv_values() loads secrets from .env → git archive creates tarball of HEAD → POST to /api/v1/deploy with tarball + project_name + secrets → polls /api/v1/deploy/{id}/status until running/error → displays agent URL
|
|
6
6
|
State/Effects: creates temporary tarball file in tempdir | reads .co/config.toml, .env files | makes network POST request | prints progress to stdout via rich.Console | does not modify project files
|
|
7
7
|
Integration: exposes handle_deploy() for CLI | expects git repo with .co/config.toml containing project.name, project.secrets, deploy.entrypoint | uses Bearer token auth | returns void (prints results)
|
|
8
|
-
Performance: git archive is fast | network timeout
|
|
8
|
+
Performance: git archive is fast | network timeout 600s for upload+build, 10s for status checks | polls every 3s up to 100 times (~5 min)
|
|
9
9
|
Errors: fails if not git repo | fails if not ConnectOnion project (.co/config.toml missing) | fails if no API key | prints backend error messages
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import json
|
|
12
13
|
import os
|
|
14
|
+
import re
|
|
13
15
|
import subprocess
|
|
14
16
|
import tempfile
|
|
15
17
|
import time
|
|
@@ -24,6 +26,30 @@ console = Console()
|
|
|
24
26
|
API_BASE = "https://oo.openonion.ai"
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def _check_host_export(entrypoint: str) -> bool:
|
|
30
|
+
"""Check if entrypoint file exports an ASGI app via host().
|
|
31
|
+
|
|
32
|
+
Looks for patterns like:
|
|
33
|
+
- host(agent)
|
|
34
|
+
- host(my_agent)
|
|
35
|
+
- from connectonion import host
|
|
36
|
+
"""
|
|
37
|
+
entrypoint_path = Path(entrypoint)
|
|
38
|
+
if not entrypoint_path.exists():
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
content = entrypoint_path.read_text()
|
|
42
|
+
|
|
43
|
+
# Check for host() call pattern
|
|
44
|
+
# Matches: host(agent), host(my_agent), host( agent ), etc.
|
|
45
|
+
host_call_pattern = r'\bhost\s*\([^)]+\)'
|
|
46
|
+
|
|
47
|
+
if re.search(host_call_pattern, content):
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
27
53
|
def _get_api_key() -> str:
|
|
28
54
|
"""Get OPENONION_API_KEY from env or .env files."""
|
|
29
55
|
if api_key := os.getenv("OPENONION_API_KEY"):
|
|
@@ -65,6 +91,26 @@ def handle_deploy():
|
|
|
65
91
|
secrets_path = config.get("project", {}).get("secrets", ".env")
|
|
66
92
|
entrypoint = config.get("deploy", {}).get("entrypoint", "agent.py")
|
|
67
93
|
|
|
94
|
+
# Validate entrypoint exists
|
|
95
|
+
if not Path(entrypoint).exists():
|
|
96
|
+
console.print(f"[red]Entrypoint not found: {entrypoint}[/red]")
|
|
97
|
+
console.print("[dim]Set entrypoint in .co/config.toml under [deploy][/dim]")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Validate entrypoint exports ASGI app via host()
|
|
101
|
+
if not _check_host_export(entrypoint):
|
|
102
|
+
console.print(f"[red]Entrypoint '{entrypoint}' does not export an ASGI app.[/red]")
|
|
103
|
+
console.print()
|
|
104
|
+
console.print("[yellow]To deploy, your agent must call host():[/yellow]")
|
|
105
|
+
console.print()
|
|
106
|
+
console.print(" [cyan]from connectonion import Agent, host[/cyan]")
|
|
107
|
+
console.print()
|
|
108
|
+
console.print(" [cyan]agent = Agent('my-agent', ...)[/cyan]")
|
|
109
|
+
console.print(" [cyan]host(agent) # Starts HTTP server[/cyan]")
|
|
110
|
+
console.print()
|
|
111
|
+
console.print("[dim]See: https://docs.connectonion.com/deploy[/dim]")
|
|
112
|
+
return
|
|
113
|
+
|
|
68
114
|
# Load secrets from .env
|
|
69
115
|
secrets = dotenv_values(secrets_path) if Path(secrets_path).exists() else {}
|
|
70
116
|
|
|
@@ -76,7 +122,17 @@ def handle_deploy():
|
|
|
76
122
|
check=True,
|
|
77
123
|
)
|
|
78
124
|
|
|
125
|
+
# Show package size
|
|
126
|
+
tarball_size = tarball_path.stat().st_size
|
|
127
|
+
if tarball_size < 1024:
|
|
128
|
+
size_str = f"{tarball_size} B"
|
|
129
|
+
elif tarball_size < 1024 * 1024:
|
|
130
|
+
size_str = f"{tarball_size / 1024:.1f} KB"
|
|
131
|
+
else:
|
|
132
|
+
size_str = f"{tarball_size / (1024 * 1024):.2f} MB"
|
|
133
|
+
|
|
79
134
|
console.print(f" Project: {project_name}")
|
|
135
|
+
console.print(f" Package: {size_str}")
|
|
80
136
|
console.print(f" Secrets: {len(secrets)} keys")
|
|
81
137
|
console.print()
|
|
82
138
|
|
|
@@ -88,39 +144,77 @@ def handle_deploy():
|
|
|
88
144
|
files={"package": ("agent.tar.gz", f, "application/gzip")},
|
|
89
145
|
data={
|
|
90
146
|
"project_name": project_name,
|
|
91
|
-
"secrets":
|
|
147
|
+
"secrets": json.dumps(secrets),
|
|
92
148
|
"entrypoint": entrypoint,
|
|
93
149
|
},
|
|
94
150
|
headers={"Authorization": f"Bearer {api_key}"},
|
|
95
|
-
timeout=
|
|
151
|
+
timeout=600, # 10 minutes for docker build
|
|
96
152
|
)
|
|
97
153
|
|
|
98
154
|
if response.status_code != 200:
|
|
99
155
|
console.print(f"[red]Deploy failed: {response.text}[/red]")
|
|
100
156
|
return
|
|
101
157
|
|
|
102
|
-
|
|
158
|
+
result = response.json()
|
|
159
|
+
|
|
160
|
+
# Check for error response (backend returns 200 with error dict)
|
|
161
|
+
if "error" in result:
|
|
162
|
+
console.print(f"[red]Deploy failed: {result['error']}[/red]")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
deployment_id = result.get("id")
|
|
166
|
+
url = result.get("url", "")
|
|
103
167
|
|
|
104
168
|
# Wait for deployment
|
|
105
169
|
console.print("Building...")
|
|
170
|
+
deploy_success = False
|
|
171
|
+
final_status = "unknown"
|
|
172
|
+
timeout_count = 0
|
|
173
|
+
|
|
106
174
|
for _ in range(100):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
175
|
+
try:
|
|
176
|
+
status_resp = requests.get(
|
|
177
|
+
f"{API_BASE}/api/v1/deploy/{deployment_id}/status",
|
|
178
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
179
|
+
timeout=30, # Increased timeout for slow SSH
|
|
180
|
+
)
|
|
181
|
+
except requests.exceptions.Timeout:
|
|
182
|
+
timeout_count += 1
|
|
183
|
+
if timeout_count >= 3:
|
|
184
|
+
console.print("[yellow]Status checks timing out, but deploy may still succeed.[/yellow]")
|
|
185
|
+
break
|
|
186
|
+
time.sleep(3)
|
|
187
|
+
continue
|
|
188
|
+
except requests.exceptions.RequestException as e:
|
|
189
|
+
console.print(f"[yellow]Network error: {e}[/yellow]")
|
|
190
|
+
time.sleep(3)
|
|
191
|
+
continue
|
|
192
|
+
|
|
112
193
|
if status_resp.status_code != 200:
|
|
194
|
+
console.print(f"[red]Status check failed: {status_resp.status_code}[/red]")
|
|
113
195
|
break
|
|
114
|
-
|
|
115
|
-
|
|
196
|
+
|
|
197
|
+
result = status_resp.json()
|
|
198
|
+
final_status = result.get("status", "unknown")
|
|
199
|
+
|
|
200
|
+
if final_status == "running":
|
|
201
|
+
deploy_success = True
|
|
202
|
+
# Update URL from status response (may be more up-to-date)
|
|
203
|
+
url = result.get("url") or url
|
|
116
204
|
break
|
|
117
|
-
if
|
|
118
|
-
console.print(f"[red]{
|
|
205
|
+
if final_status in ("error", "failed"):
|
|
206
|
+
console.print(f"[red]Deploy failed: {result.get('error_message', 'Unknown error')}[/red]")
|
|
119
207
|
return
|
|
120
208
|
time.sleep(3)
|
|
121
209
|
|
|
122
|
-
url = response.json().get("url", "")
|
|
123
210
|
console.print()
|
|
124
|
-
|
|
125
|
-
|
|
211
|
+
if deploy_success:
|
|
212
|
+
console.print("[bold green]Deployed![/bold green]")
|
|
213
|
+
else:
|
|
214
|
+
console.print(f"[yellow]Deploy status: {final_status}[/yellow]")
|
|
215
|
+
console.print("[yellow]Check status with 'co deployments' or try again.[/yellow]")
|
|
216
|
+
|
|
217
|
+
# Always show URL if we have one
|
|
218
|
+
if url:
|
|
219
|
+
console.print(f"Agent URL: {url}")
|
|
126
220
|
console.print()
|
|
@@ -221,79 +221,55 @@ def verify_signature(payload: dict, signature: str, public_key: str) -> bool:
|
|
|
221
221
|
def extract_and_authenticate(data: dict, trust, *, blacklist=None, whitelist=None, agent_address=None):
|
|
222
222
|
"""Extract prompt and authenticate request.
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
ALL requests must be signed - this is a protocol requirement.
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
{"prompt": "...", "from": "0x..."}
|
|
228
|
-
|
|
229
|
-
Signed (Ed25519):
|
|
226
|
+
Required format (Ed25519 signed):
|
|
230
227
|
{
|
|
231
|
-
"payload": {"prompt": "...", "to": "
|
|
232
|
-
"from": "
|
|
233
|
-
"signature": "
|
|
228
|
+
"payload": {"prompt": "...", "to": "0xAgentAddress", "timestamp": 123},
|
|
229
|
+
"from": "0xCallerPublicKey",
|
|
230
|
+
"signature": "0xEd25519Signature..."
|
|
234
231
|
}
|
|
235
232
|
|
|
236
|
-
Trust
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
233
|
+
Trust levels control additional policies AFTER signature verification:
|
|
234
|
+
- "open": Any valid signer allowed
|
|
235
|
+
- "careful": Warnings for unknown signers (default)
|
|
236
|
+
- "strict": Whitelist only
|
|
237
|
+
- Custom policy/Agent: LLM evaluation
|
|
240
238
|
|
|
241
239
|
Returns: (prompt, identity, sig_valid, error)
|
|
242
240
|
"""
|
|
243
|
-
#
|
|
244
|
-
if
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
data, trust_level, blacklist=blacklist, whitelist=whitelist, agent_address=agent_address
|
|
252
|
-
)
|
|
253
|
-
else:
|
|
254
|
-
prompt, identity, sig_valid, error = _authenticate_simple(
|
|
255
|
-
data, trust_level, blacklist=blacklist, whitelist=whitelist
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
# If basic auth failed, return error
|
|
241
|
+
# Protocol requirement: ALL requests must be signed
|
|
242
|
+
if "payload" not in data or "signature" not in data:
|
|
243
|
+
return None, None, False, "unauthorized: signed request required"
|
|
244
|
+
|
|
245
|
+
# Verify signature (protocol level - always required)
|
|
246
|
+
prompt, identity, error = _authenticate_signed(
|
|
247
|
+
data, blacklist=blacklist, whitelist=whitelist, agent_address=agent_address
|
|
248
|
+
)
|
|
259
249
|
if error:
|
|
260
|
-
return prompt, identity,
|
|
250
|
+
return prompt, identity, False, error
|
|
261
251
|
|
|
262
|
-
#
|
|
263
|
-
|
|
264
|
-
|
|
252
|
+
# Trust level: additional policies AFTER signature verification
|
|
253
|
+
if trust == "strict" and whitelist and identity not in whitelist:
|
|
254
|
+
return None, identity, True, "forbidden: not in whitelist"
|
|
255
|
+
|
|
256
|
+
# Custom trust policy/agent evaluation
|
|
257
|
+
if is_custom_trust(trust):
|
|
265
258
|
trust_agent = create_trust_agent(trust)
|
|
266
|
-
accepted, reason = evaluate_with_trust_agent(trust_agent, prompt, identity,
|
|
259
|
+
accepted, reason = evaluate_with_trust_agent(trust_agent, prompt, identity, True)
|
|
267
260
|
if not accepted:
|
|
268
|
-
return None, identity,
|
|
269
|
-
|
|
270
|
-
return prompt, identity, sig_valid, None
|
|
271
|
-
|
|
261
|
+
return None, identity, True, f"rejected: {reason}"
|
|
272
262
|
|
|
273
|
-
|
|
274
|
-
"""Authenticate simple unsigned request."""
|
|
275
|
-
prompt = data.get("prompt", "")
|
|
276
|
-
identity = data.get("from")
|
|
277
|
-
|
|
278
|
-
# Check blacklist
|
|
279
|
-
if blacklist and identity in blacklist:
|
|
280
|
-
return None, identity, False, "forbidden: blacklisted"
|
|
281
|
-
|
|
282
|
-
# Check whitelist (bypass other checks)
|
|
283
|
-
if whitelist and identity in whitelist:
|
|
284
|
-
return prompt, identity, True, None
|
|
263
|
+
return prompt, identity, True, None
|
|
285
264
|
|
|
286
|
-
# Trust level enforcement
|
|
287
|
-
if trust == "strict" and not identity:
|
|
288
|
-
return None, None, False, "unauthorized: identity required"
|
|
289
265
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return prompt, identity, sig_valid, None
|
|
266
|
+
def _authenticate_signed(data: dict, *, blacklist=None, whitelist=None, agent_address=None):
|
|
267
|
+
"""Authenticate signed request with Ed25519 - ALWAYS REQUIRED.
|
|
293
268
|
|
|
269
|
+
Protocol-level signature verification. All requests must be signed.
|
|
294
270
|
|
|
295
|
-
|
|
296
|
-
"""
|
|
271
|
+
Returns: (prompt, identity, error) - error is None on success
|
|
272
|
+
"""
|
|
297
273
|
payload = data.get("payload", {})
|
|
298
274
|
identity = data.get("from")
|
|
299
275
|
signature = data.get("signature")
|
|
@@ -304,34 +280,34 @@ def _authenticate_signed(data: dict, trust: str, *, blacklist=None, whitelist=No
|
|
|
304
280
|
|
|
305
281
|
# Check blacklist first
|
|
306
282
|
if blacklist and identity in blacklist:
|
|
307
|
-
return None, identity,
|
|
283
|
+
return None, identity, "forbidden: blacklisted"
|
|
308
284
|
|
|
309
|
-
# Check whitelist (bypass signature check)
|
|
285
|
+
# Check whitelist (bypass signature check - trusted caller)
|
|
310
286
|
if whitelist and identity in whitelist:
|
|
311
|
-
return prompt, identity,
|
|
287
|
+
return prompt, identity, None
|
|
312
288
|
|
|
313
|
-
# Validate required fields
|
|
289
|
+
# Validate required fields
|
|
314
290
|
if not identity:
|
|
315
|
-
return None, None,
|
|
291
|
+
return None, None, "unauthorized: 'from' field required"
|
|
316
292
|
if not signature:
|
|
317
|
-
return None, identity,
|
|
293
|
+
return None, identity, "unauthorized: signature required"
|
|
318
294
|
if not timestamp:
|
|
319
|
-
return None, identity,
|
|
295
|
+
return None, identity, "unauthorized: timestamp required in payload"
|
|
320
296
|
|
|
321
297
|
# Check timestamp expiry (5 minute window)
|
|
322
298
|
now = time.time()
|
|
323
299
|
if abs(now - timestamp) > SIGNATURE_EXPIRY_SECONDS:
|
|
324
|
-
return None, identity,
|
|
300
|
+
return None, identity, "unauthorized: signature expired"
|
|
325
301
|
|
|
326
302
|
# Optionally verify 'to' matches agent address
|
|
327
303
|
if agent_address and to_address and to_address != agent_address:
|
|
328
|
-
return None, identity,
|
|
304
|
+
return None, identity, "unauthorized: wrong recipient"
|
|
329
305
|
|
|
330
306
|
# Verify signature
|
|
331
307
|
if not verify_signature(payload, signature, identity):
|
|
332
|
-
return None, identity,
|
|
308
|
+
return None, identity, "unauthorized: invalid signature"
|
|
333
309
|
|
|
334
|
-
return prompt, identity,
|
|
310
|
+
return prompt, identity, None
|
|
335
311
|
|
|
336
312
|
|
|
337
313
|
def get_agent_address(agent) -> str:
|
|
@@ -381,6 +357,35 @@ def is_custom_trust(trust) -> bool:
|
|
|
381
357
|
return trust not in TRUST_LEVELS # It's a policy string
|
|
382
358
|
|
|
383
359
|
|
|
360
|
+
# === Admin Handlers ===
|
|
361
|
+
|
|
362
|
+
def admin_logs_handler(agent_name: str) -> dict:
|
|
363
|
+
"""GET /admin/logs - return plain text activity log file."""
|
|
364
|
+
log_path = Path(f".co/logs/{agent_name}.log")
|
|
365
|
+
if log_path.exists():
|
|
366
|
+
return {"content": log_path.read_text()}
|
|
367
|
+
return {"error": "No logs found"}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def admin_sessions_handler() -> dict:
|
|
371
|
+
"""GET /admin/sessions - return all activity sessions as JSON array."""
|
|
372
|
+
import yaml
|
|
373
|
+
sessions_dir = Path(".co/sessions")
|
|
374
|
+
if not sessions_dir.exists():
|
|
375
|
+
return {"sessions": []}
|
|
376
|
+
|
|
377
|
+
sessions = []
|
|
378
|
+
for session_file in sessions_dir.glob("*.yaml"):
|
|
379
|
+
with open(session_file) as f:
|
|
380
|
+
session_data = yaml.safe_load(f)
|
|
381
|
+
if session_data:
|
|
382
|
+
sessions.append(session_data)
|
|
383
|
+
|
|
384
|
+
# Sort by created date descending (newest first)
|
|
385
|
+
sessions.sort(key=lambda s: s.get("created", ""), reverse=True)
|
|
386
|
+
return {"sessions": sessions}
|
|
387
|
+
|
|
388
|
+
|
|
384
389
|
# === Entry Point ===
|
|
385
390
|
|
|
386
391
|
def _create_handlers(agent, result_ttl: int):
|
|
@@ -393,6 +398,9 @@ def _create_handlers(agent, result_ttl: int):
|
|
|
393
398
|
"info": lambda trust: info_handler(agent, trust),
|
|
394
399
|
"auth": extract_and_authenticate,
|
|
395
400
|
"ws_input": agent.input,
|
|
401
|
+
# Admin endpoints (auth required via OPENONION_API_KEY)
|
|
402
|
+
"admin_logs": lambda: admin_logs_handler(agent.name),
|
|
403
|
+
"admin_sessions": admin_sessions_handler,
|
|
396
404
|
}
|
|
397
405
|
|
|
398
406
|
|
|
@@ -428,7 +436,7 @@ def _start_relay_background(agent, relay_url: str, addr_data: dict):
|
|
|
428
436
|
|
|
429
437
|
def host(
|
|
430
438
|
agent,
|
|
431
|
-
port: int =
|
|
439
|
+
port: int = None,
|
|
432
440
|
trust: Union[str, "Agent"] = "careful",
|
|
433
441
|
result_ttl: int = 86400,
|
|
434
442
|
workers: int = 1,
|
|
@@ -443,7 +451,7 @@ def host(
|
|
|
443
451
|
|
|
444
452
|
Args:
|
|
445
453
|
agent: Agent to host
|
|
446
|
-
port: HTTP port (default 8000)
|
|
454
|
+
port: HTTP port (default: PORT env var or 8000)
|
|
447
455
|
trust: Trust level, policy, or Agent:
|
|
448
456
|
- Level: "open", "careful", "strict"
|
|
449
457
|
- Policy: Natural language or file path
|
|
@@ -463,10 +471,16 @@ def host(
|
|
|
463
471
|
GET /health - Health check
|
|
464
472
|
GET /info - Agent info
|
|
465
473
|
WS /ws - WebSocket
|
|
474
|
+
GET /logs - Activity log (requires OPENONION_API_KEY)
|
|
475
|
+
GET /logs/sessions - Activity sessions (requires OPENONION_API_KEY)
|
|
466
476
|
"""
|
|
467
477
|
import uvicorn
|
|
468
478
|
from . import address
|
|
469
479
|
|
|
480
|
+
# Use PORT env var if port not specified (for container deployments)
|
|
481
|
+
if port is None:
|
|
482
|
+
port = int(os.environ.get("PORT", 8000))
|
|
483
|
+
|
|
470
484
|
# Load or generate agent identity
|
|
471
485
|
co_dir = Path.cwd() / '.co'
|
|
472
486
|
addr_data = address.load(co_dir)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/meta-agent/prompts/metagent.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.5 → connectonion-0.5.7}/connectonion/cli/templates/playwright/requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_agent/prompts/debug_assistant.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.5 → connectonion-0.5.7}/connectonion/debug_explainer/root_cause_analysis_prompt.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.5 → connectonion-0.5.7}/connectonion/execution_analyzer/execution_analysis.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.5 → connectonion-0.5.7}/connectonion/useful_plugins/image_result_formatter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|