connectonion 0.5.10__py3-none-any.whl → 0.6.1__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 +17 -16
- connectonion/cli/browser_agent/browser.py +488 -145
- connectonion/cli/browser_agent/scroll_strategies.py +276 -0
- connectonion/cli/commands/copy_commands.py +24 -1
- connectonion/cli/commands/deploy_commands.py +15 -0
- connectonion/cli/commands/eval_commands.py +286 -0
- connectonion/cli/commands/project_cmd_lib.py +1 -1
- connectonion/cli/main.py +11 -0
- connectonion/console.py +5 -5
- connectonion/core/__init__.py +53 -0
- connectonion/{agent.py → core/agent.py} +18 -15
- connectonion/{llm.py → core/llm.py} +9 -19
- connectonion/{tool_executor.py → core/tool_executor.py} +3 -2
- connectonion/{tool_factory.py → core/tool_factory.py} +3 -1
- connectonion/debug/__init__.py +51 -0
- connectonion/{interactive_debugger.py → debug/auto_debug.py} +7 -7
- connectonion/{auto_debug_exception.py → debug/auto_debug_exception.py} +3 -3
- connectonion/{debugger_ui.py → debug/auto_debug_ui.py} +1 -1
- connectonion/{debug_explainer → debug/debug_explainer}/explain_agent.py +1 -1
- connectonion/{debug_explainer → debug/debug_explainer}/explain_context.py +1 -1
- connectonion/{execution_analyzer → debug/execution_analyzer}/execution_analysis.py +1 -1
- connectonion/debug/runtime_inspector/__init__.py +13 -0
- connectonion/{debug_agent → debug/runtime_inspector}/agent.py +1 -1
- connectonion/{xray.py → debug/xray.py} +1 -1
- connectonion/llm_do.py +1 -1
- connectonion/logger.py +305 -135
- connectonion/network/__init__.py +37 -0
- connectonion/{announce.py → network/announce.py} +1 -1
- connectonion/{asgi.py → network/asgi.py} +122 -2
- connectonion/{connect.py → network/connect.py} +1 -1
- connectonion/network/connection.py +123 -0
- connectonion/{host.py → network/host.py} +31 -11
- connectonion/{trust.py → network/trust.py} +1 -1
- connectonion/tui/__init__.py +22 -0
- connectonion/tui/chat.py +647 -0
- connectonion/useful_events_handlers/reflect.py +2 -2
- connectonion/useful_plugins/__init__.py +4 -3
- connectonion/useful_plugins/calendar_plugin.py +2 -2
- connectonion/useful_plugins/eval.py +2 -2
- connectonion/useful_plugins/gmail_plugin.py +2 -2
- connectonion/useful_plugins/image_result_formatter.py +2 -2
- connectonion/useful_plugins/re_act.py +2 -2
- connectonion/useful_plugins/shell_approval.py +2 -2
- connectonion/useful_plugins/ui_stream.py +164 -0
- {connectonion-0.5.10.dist-info → connectonion-0.6.1.dist-info}/METADATA +4 -3
- connectonion-0.6.1.dist-info/RECORD +123 -0
- connectonion/debug_agent/__init__.py +0 -13
- connectonion-0.5.10.dist-info/RECORD +0 -115
- /connectonion/{events.py → core/events.py} +0 -0
- /connectonion/{tool_registry.py → core/tool_registry.py} +0 -0
- /connectonion/{usage.py → core/usage.py} +0 -0
- /connectonion/{debug_explainer → debug/debug_explainer}/__init__.py +0 -0
- /connectonion/{debug_explainer → debug/debug_explainer}/explainer_prompt.md +0 -0
- /connectonion/{debug_explainer → debug/debug_explainer}/root_cause_analysis_prompt.md +0 -0
- /connectonion/{decorators.py → debug/decorators.py} +0 -0
- /connectonion/{execution_analyzer → debug/execution_analyzer}/__init__.py +0 -0
- /connectonion/{execution_analyzer → debug/execution_analyzer}/execution_analysis_prompt.md +0 -0
- /connectonion/{debug_agent → debug/runtime_inspector}/prompts/debug_assistant.md +0 -0
- /connectonion/{debug_agent → debug/runtime_inspector}/runtime_inspector.py +0 -0
- /connectonion/{relay.py → network/relay.py} +0 -0
- /connectonion/{static → network/static}/docs.html +0 -0
- /connectonion/{trust_agents.py → network/trust_agents.py} +0 -0
- /connectonion/{trust_functions.py → network/trust_functions.py} +0 -0
- {connectonion-0.5.10.dist-info → connectonion-0.6.1.dist-info}/WHEEL +0 -0
- {connectonion-0.5.10.dist-info → connectonion-0.6.1.dist-info}/entry_points.txt +0 -0
|
@@ -6,13 +6,48 @@ 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 asyncio
|
|
9
10
|
import hmac
|
|
10
11
|
import json
|
|
11
12
|
import os
|
|
13
|
+
import queue
|
|
14
|
+
import threading
|
|
12
15
|
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict
|
|
13
17
|
|
|
14
18
|
from pydantic import BaseModel
|
|
15
19
|
|
|
20
|
+
from .connection import Connection
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AsyncToSyncConnection(Connection):
|
|
24
|
+
"""Bridge async WebSocket to sync Connection interface.
|
|
25
|
+
|
|
26
|
+
Uses queues to communicate between async WebSocket handler and sync agent code.
|
|
27
|
+
The agent runs in a thread, sending/receiving via queues.
|
|
28
|
+
The async handler pumps messages between WebSocket and queues.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._outgoing: queue.Queue[Dict[str, Any]] = queue.Queue()
|
|
33
|
+
self._incoming: queue.Queue[Dict[str, Any]] = queue.Queue()
|
|
34
|
+
self._closed = False
|
|
35
|
+
|
|
36
|
+
def send(self, event: Dict[str, Any]) -> None:
|
|
37
|
+
"""Queue event to be sent to client."""
|
|
38
|
+
if not self._closed:
|
|
39
|
+
self._outgoing.put(event)
|
|
40
|
+
|
|
41
|
+
def receive(self) -> Dict[str, Any]:
|
|
42
|
+
"""Block until response from client."""
|
|
43
|
+
return self._incoming.get()
|
|
44
|
+
|
|
45
|
+
def close(self):
|
|
46
|
+
"""Mark connection as closed."""
|
|
47
|
+
self._closed = True
|
|
48
|
+
# Unblock any waiting receive
|
|
49
|
+
self._incoming.put({"type": "connection_closed"})
|
|
50
|
+
|
|
16
51
|
|
|
17
52
|
def _json_default(obj):
|
|
18
53
|
"""Handle non-serializable objects like Pydantic models.
|
|
@@ -190,6 +225,11 @@ async def handle_websocket(
|
|
|
190
225
|
):
|
|
191
226
|
"""Handle WebSocket connections at /ws.
|
|
192
227
|
|
|
228
|
+
Supports bidirectional communication via Connection interface:
|
|
229
|
+
- Agent sends events via connection.log() / connection.send()
|
|
230
|
+
- Agent requests approval via connection.request_approval()
|
|
231
|
+
- Client responds to approval requests
|
|
232
|
+
|
|
193
233
|
Args:
|
|
194
234
|
scope: ASGI scope dict
|
|
195
235
|
receive: ASGI receive callable
|
|
@@ -229,9 +269,89 @@ async def handle_websocket(
|
|
|
229
269
|
await send({"type": "websocket.send",
|
|
230
270
|
"text": json.dumps({"type": "ERROR", "message": "prompt required"})})
|
|
231
271
|
continue
|
|
232
|
-
|
|
272
|
+
|
|
273
|
+
# Create connection for bidirectional communication
|
|
274
|
+
connection = AsyncToSyncConnection()
|
|
275
|
+
agent_done = threading.Event()
|
|
276
|
+
result_holder = [None]
|
|
277
|
+
|
|
278
|
+
def run_agent():
|
|
279
|
+
result_holder[0] = handlers["ws_input"](prompt, connection)
|
|
280
|
+
agent_done.set()
|
|
281
|
+
|
|
282
|
+
# Start agent in thread
|
|
283
|
+
agent_thread = threading.Thread(target=run_agent, daemon=True)
|
|
284
|
+
agent_thread.start()
|
|
285
|
+
|
|
286
|
+
# Pump messages between WebSocket and connection
|
|
287
|
+
await _pump_messages(receive, send, connection, agent_done)
|
|
288
|
+
|
|
289
|
+
# Send final result
|
|
233
290
|
await send({"type": "websocket.send",
|
|
234
|
-
"text": json.dumps({"type": "OUTPUT", "result":
|
|
291
|
+
"text": json.dumps({"type": "OUTPUT", "result": result_holder[0]})})
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
async def _pump_messages(ws_receive, ws_send, connection: AsyncToSyncConnection, agent_done: threading.Event):
|
|
295
|
+
"""Pump messages between WebSocket and connection queues.
|
|
296
|
+
|
|
297
|
+
Runs until agent completes. Handles:
|
|
298
|
+
- Outgoing: connection._outgoing queue → WebSocket
|
|
299
|
+
- Incoming: WebSocket → connection._incoming queue (for approval responses)
|
|
300
|
+
"""
|
|
301
|
+
loop = asyncio.get_event_loop()
|
|
302
|
+
|
|
303
|
+
async def send_outgoing():
|
|
304
|
+
"""Send outgoing messages from connection to WebSocket."""
|
|
305
|
+
while not agent_done.is_set():
|
|
306
|
+
# Use run_in_executor for blocking queue.get
|
|
307
|
+
try:
|
|
308
|
+
event = await loop.run_in_executor(
|
|
309
|
+
None, lambda: connection._outgoing.get(timeout=0.05)
|
|
310
|
+
)
|
|
311
|
+
await ws_send({"type": "websocket.send", "text": json.dumps(event)})
|
|
312
|
+
except queue.Empty:
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
# Drain remaining
|
|
316
|
+
while True:
|
|
317
|
+
try:
|
|
318
|
+
event = connection._outgoing.get_nowait()
|
|
319
|
+
await ws_send({"type": "websocket.send", "text": json.dumps(event)})
|
|
320
|
+
except queue.Empty:
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
async def receive_incoming():
|
|
324
|
+
"""Receive incoming messages from WebSocket to connection."""
|
|
325
|
+
while not agent_done.is_set():
|
|
326
|
+
try:
|
|
327
|
+
msg = await asyncio.wait_for(ws_receive(), timeout=0.1)
|
|
328
|
+
if msg["type"] == "websocket.receive":
|
|
329
|
+
try:
|
|
330
|
+
data = json.loads(msg.get("text", "{}"))
|
|
331
|
+
connection._incoming.put(data)
|
|
332
|
+
except json.JSONDecodeError:
|
|
333
|
+
pass
|
|
334
|
+
elif msg["type"] == "websocket.disconnect":
|
|
335
|
+
connection.close()
|
|
336
|
+
break
|
|
337
|
+
except asyncio.TimeoutError:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
# Run both tasks concurrently
|
|
341
|
+
send_task = asyncio.create_task(send_outgoing())
|
|
342
|
+
recv_task = asyncio.create_task(receive_incoming())
|
|
343
|
+
|
|
344
|
+
# Wait for agent to complete
|
|
345
|
+
while not agent_done.is_set():
|
|
346
|
+
await asyncio.sleep(0.05)
|
|
347
|
+
|
|
348
|
+
# Cancel receive task and wait for send to finish draining
|
|
349
|
+
recv_task.cancel()
|
|
350
|
+
try:
|
|
351
|
+
await recv_task
|
|
352
|
+
except asyncio.CancelledError:
|
|
353
|
+
pass
|
|
354
|
+
await send_task
|
|
235
355
|
|
|
236
356
|
|
|
237
357
|
def create_app(
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Connection interface for agent-client communication during hosted execution
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [abc, typing] | imported by [asgi.py, __init__.py] | tested by [tests/unit/test_connection.py]
|
|
5
|
+
Data flow: receives from host/asgi → WebSocket send/receive → provides log() and request_approval() to agent event handlers
|
|
6
|
+
State/Effects: stateless base class | WebSocketConnection wraps ASGI WebSocket for bidirectional messaging
|
|
7
|
+
Integration: exposes Connection (base), WebSocketConnection (ASGI adapter) | agent.connection set by host() during WebSocket requests
|
|
8
|
+
Performance: log() is fire-and-forget (non-blocking) | request_approval() blocks waiting for client response
|
|
9
|
+
Errors: WebSocketConnection raises if WebSocket closed unexpectedly
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Connection(ABC):
|
|
17
|
+
"""Base connection interface for agent-client communication.
|
|
18
|
+
|
|
19
|
+
Two-layer API:
|
|
20
|
+
- Low-level: send(event), receive() - primitives for any communication
|
|
21
|
+
- High-level: log(type, **data), request_approval(tool, args) - common patterns
|
|
22
|
+
|
|
23
|
+
Usage in event handlers:
|
|
24
|
+
@after_llm
|
|
25
|
+
def on_thinking(agent):
|
|
26
|
+
if agent.connection:
|
|
27
|
+
agent.connection.log("thinking")
|
|
28
|
+
|
|
29
|
+
@before_each_tool
|
|
30
|
+
def on_tool(agent):
|
|
31
|
+
if agent.connection:
|
|
32
|
+
tool = agent.current_session['pending_tool']
|
|
33
|
+
if tool['name'] in DANGEROUS:
|
|
34
|
+
if not agent.connection.request_approval(tool['name'], tool['arguments']):
|
|
35
|
+
raise ToolRejected()
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# ═══════════════════════════════════════════════════════
|
|
39
|
+
# LOW-LEVEL API (Primitives)
|
|
40
|
+
# ═══════════════════════════════════════════════════════
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def send(self, event: Dict[str, Any]) -> None:
|
|
44
|
+
"""Send any event to client.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
event: Dict with at least 'type' key, e.g. {"type": "thinking"}
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def receive(self) -> Dict[str, Any]:
|
|
53
|
+
"""Receive response from client.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dict response from client
|
|
57
|
+
"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
# ═══════════════════════════════════════════════════════
|
|
61
|
+
# HIGH-LEVEL API (Patterns)
|
|
62
|
+
# ═══════════════════════════════════════════════════════
|
|
63
|
+
|
|
64
|
+
def log(self, event_type: str, **data) -> None:
|
|
65
|
+
"""One-way notification to client.
|
|
66
|
+
|
|
67
|
+
Common event types: thinking, tool_call, tool_result, complete, error
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
event_type: Type of event (e.g. "thinking", "tool_call")
|
|
71
|
+
**data: Additional data for the event
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
connection.log("thinking")
|
|
75
|
+
connection.log("tool_call", name="search", arguments={"q": "python"})
|
|
76
|
+
"""
|
|
77
|
+
self.send({"type": event_type, **data})
|
|
78
|
+
|
|
79
|
+
def request_approval(self, tool: str, arguments: Dict[str, Any]) -> bool:
|
|
80
|
+
"""Two-way: request permission, wait for response.
|
|
81
|
+
|
|
82
|
+
Sends approval_needed event and blocks until client responds.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
tool: Name of tool requiring approval
|
|
86
|
+
arguments: Tool arguments to show user
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
True if approved, False if rejected
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
if not connection.request_approval("delete_file", {"path": "/tmp/x"}):
|
|
93
|
+
raise ToolRejected()
|
|
94
|
+
"""
|
|
95
|
+
self.send({"type": "approval_needed", "tool": tool, "arguments": arguments})
|
|
96
|
+
response = self.receive()
|
|
97
|
+
return response.get("approved", False)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class SyncWebSocketConnection(Connection):
|
|
101
|
+
"""Synchronous WebSocket connection adapter.
|
|
102
|
+
|
|
103
|
+
Wraps async WebSocket send/receive for use in synchronous agent code.
|
|
104
|
+
Uses threading events to bridge async/sync boundary.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, send_callback, receive_callback):
|
|
108
|
+
"""Initialize with send/receive callbacks.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
send_callback: Callable that sends message to WebSocket
|
|
112
|
+
receive_callback: Callable that receives message from WebSocket
|
|
113
|
+
"""
|
|
114
|
+
self._send = send_callback
|
|
115
|
+
self._receive = receive_callback
|
|
116
|
+
|
|
117
|
+
def send(self, event: Dict[str, Any]) -> None:
|
|
118
|
+
"""Send event to client via WebSocket."""
|
|
119
|
+
self._send(event)
|
|
120
|
+
|
|
121
|
+
def receive(self) -> Dict[str, Any]:
|
|
122
|
+
"""Receive response from client via WebSocket."""
|
|
123
|
+
return self._receive()
|
|
@@ -177,8 +177,8 @@ def health_handler(agent, start_time: float) -> dict:
|
|
|
177
177
|
|
|
178
178
|
def info_handler(agent, trust: str) -> dict:
|
|
179
179
|
"""GET /info"""
|
|
180
|
-
from
|
|
181
|
-
tools = agent.tools.
|
|
180
|
+
from .. import __version__
|
|
181
|
+
tools = agent.tools.names() if hasattr(agent.tools, "names") else []
|
|
182
182
|
return {
|
|
183
183
|
"name": agent.name,
|
|
184
184
|
"address": get_agent_address(agent),
|
|
@@ -338,7 +338,7 @@ def evaluate_with_trust_agent(trust_agent, prompt: str, identity: str, sig_valid
|
|
|
338
338
|
(accepted, reason) tuple
|
|
339
339
|
"""
|
|
340
340
|
from pydantic import BaseModel
|
|
341
|
-
from
|
|
341
|
+
from ..llm_do import llm_do
|
|
342
342
|
|
|
343
343
|
class TrustDecision(BaseModel):
|
|
344
344
|
accept: bool
|
|
@@ -382,7 +382,7 @@ def admin_sessions_handler() -> dict:
|
|
|
382
382
|
Frontend handles the display logic.
|
|
383
383
|
"""
|
|
384
384
|
import yaml
|
|
385
|
-
sessions_dir = Path(".co/
|
|
385
|
+
sessions_dir = Path(".co/evals")
|
|
386
386
|
if not sessions_dir.exists():
|
|
387
387
|
return {"sessions": []}
|
|
388
388
|
|
|
@@ -401,10 +401,17 @@ def admin_sessions_handler() -> dict:
|
|
|
401
401
|
# === Entry Point ===
|
|
402
402
|
|
|
403
403
|
def _create_handlers(agent_template, result_ttl: int):
|
|
404
|
-
"""Create handler dict for ASGI app.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
404
|
+
"""Create handler dict for ASGI app.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
agent_template: Agent used as template (deep-copied per request for isolation)
|
|
408
|
+
result_ttl: How long to keep results on server in seconds
|
|
409
|
+
"""
|
|
410
|
+
def ws_input(prompt: str, connection) -> str:
|
|
411
|
+
"""WebSocket input with bidirectional connection support."""
|
|
412
|
+
agent_template.reset_conversation()
|
|
413
|
+
agent_template.connection = connection
|
|
414
|
+
return agent_template.input(prompt)
|
|
408
415
|
|
|
409
416
|
return {
|
|
410
417
|
"input": lambda storage, prompt, ttl, session=None: input_handler(agent_template, storage, prompt, ttl, session),
|
|
@@ -425,6 +432,11 @@ def _start_relay_background(agent_template, relay_url: str, addr_data: dict):
|
|
|
425
432
|
|
|
426
433
|
The relay connection runs alongside the HTTP server, allowing the agent
|
|
427
434
|
to be discovered via P2P network while also serving HTTP requests.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
agent_template: Agent used as template (deep-copied per request for isolation)
|
|
438
|
+
relay_url: WebSocket URL for P2P relay
|
|
439
|
+
addr_data: Agent address data (public key, address)
|
|
428
440
|
"""
|
|
429
441
|
import asyncio
|
|
430
442
|
import threading
|
|
@@ -466,8 +478,12 @@ def host(
|
|
|
466
478
|
"""
|
|
467
479
|
Host an agent over HTTP/WebSocket with optional P2P relay discovery.
|
|
468
480
|
|
|
481
|
+
The agent is used as a template - each request gets a fresh deep copy
|
|
482
|
+
for complete isolation. This ensures tools with state (like BrowserTool)
|
|
483
|
+
don't interfere between concurrent requests.
|
|
484
|
+
|
|
469
485
|
Args:
|
|
470
|
-
agent: Agent
|
|
486
|
+
agent: Agent template (deep-copied per request for isolation)
|
|
471
487
|
port: HTTP port (default: PORT env var or 8000)
|
|
472
488
|
trust: Trust level, policy, or Agent:
|
|
473
489
|
- Level: "open", "careful", "strict"
|
|
@@ -492,7 +508,7 @@ def host(
|
|
|
492
508
|
GET /logs/sessions - Activity sessions (requires OPENONION_API_KEY)
|
|
493
509
|
"""
|
|
494
510
|
import uvicorn
|
|
495
|
-
from
|
|
511
|
+
from .. import address
|
|
496
512
|
|
|
497
513
|
# Use PORT env var if port not specified (for container deployments)
|
|
498
514
|
if port is None:
|
|
@@ -543,8 +559,11 @@ def host(
|
|
|
543
559
|
def _make_app(agent, trust: Union[str, "Agent"] = "careful", result_ttl=86400, *, blacklist=None, whitelist=None):
|
|
544
560
|
"""Create ASGI app for external uvicorn/gunicorn usage.
|
|
545
561
|
|
|
562
|
+
The agent is used as a template - each request gets a fresh deep copy
|
|
563
|
+
for complete isolation.
|
|
564
|
+
|
|
546
565
|
Args:
|
|
547
|
-
agent: Agent
|
|
566
|
+
agent: Agent template (deep-copied per request for isolation)
|
|
548
567
|
trust: Trust level, policy, or Agent
|
|
549
568
|
result_ttl: How long to keep results on server in seconds
|
|
550
569
|
blacklist: Blocked identities
|
|
@@ -579,6 +598,7 @@ host.app = _make_app
|
|
|
579
598
|
def create_app_compat(agent, storage, trust="careful", result_ttl=86400, *, blacklist=None, whitelist=None):
|
|
580
599
|
"""Create ASGI app (backward-compatible wrapper).
|
|
581
600
|
|
|
601
|
+
The agent is used as a template (deep-copied per request for isolation).
|
|
582
602
|
Prefer using host.app(agent) for new code.
|
|
583
603
|
"""
|
|
584
604
|
handlers = _create_handlers(agent, result_ttl)
|
|
@@ -59,7 +59,7 @@ def create_trust_agent(trust: Union[str, Path, 'Agent', None], api_key: Optional
|
|
|
59
59
|
ValueError: If trust level is invalid
|
|
60
60
|
FileNotFoundError: If trust policy file doesn't exist
|
|
61
61
|
"""
|
|
62
|
-
from .agent import Agent # Import here to avoid circular dependency
|
|
62
|
+
from ..core.agent import Agent # Import here to avoid circular dependency
|
|
63
63
|
|
|
64
64
|
# If None, check for environment default
|
|
65
65
|
if trust is None:
|
connectonion/tui/__init__.py
CHANGED
|
@@ -37,8 +37,20 @@ from .status_bar import StatusBar, SimpleStatusBar, ProgressSegment
|
|
|
37
37
|
from .divider import Divider
|
|
38
38
|
from .pick import pick
|
|
39
39
|
from .footer import Footer
|
|
40
|
+
from .chat import (
|
|
41
|
+
Chat,
|
|
42
|
+
TriggerAutoComplete,
|
|
43
|
+
ChatStatusBar,
|
|
44
|
+
HintsFooter,
|
|
45
|
+
WelcomeMessage,
|
|
46
|
+
UserMessage,
|
|
47
|
+
AssistantMessage,
|
|
48
|
+
ThinkingIndicator,
|
|
49
|
+
)
|
|
50
|
+
from textual_autocomplete import DropdownItem as CommandItem
|
|
40
51
|
|
|
41
52
|
__all__ = [
|
|
53
|
+
# Rich-based TUI (legacy)
|
|
42
54
|
"Input",
|
|
43
55
|
"Dropdown",
|
|
44
56
|
"DropdownItem",
|
|
@@ -54,4 +66,14 @@ __all__ = [
|
|
|
54
66
|
"Divider",
|
|
55
67
|
"pick",
|
|
56
68
|
"Footer",
|
|
69
|
+
# Textual-based Chat
|
|
70
|
+
"Chat",
|
|
71
|
+
"TriggerAutoComplete",
|
|
72
|
+
"CommandItem",
|
|
73
|
+
"ChatStatusBar",
|
|
74
|
+
"HintsFooter",
|
|
75
|
+
"WelcomeMessage",
|
|
76
|
+
"UserMessage",
|
|
77
|
+
"AssistantMessage",
|
|
78
|
+
"ThinkingIndicator",
|
|
57
79
|
]
|