feat(webui): polish agent output and app discovery

This commit is contained in:
Xubin Ren
2026-07-22 22:42:31 +08:00
parent b189a37648
commit aa8387fb4d
87 changed files with 6225 additions and 2379 deletions
+26 -3
View File
@@ -32,6 +32,11 @@ from nanobot.bus.outbound_events import (
from nanobot.bus.queue import MessageBus
from nanobot.channels.base import BaseChannel
from nanobot.config.schema import Base
from nanobot.runtime_context import (
RUNTIME_CONTEXT_INPUT_META,
WEBUI_QUOTE_METADATA,
webui_quote_runtime_context,
)
from nanobot.security.workspace_access import (
WORKSPACE_SCOPE_METADATA_KEY,
WorkspaceScopeError,
@@ -250,6 +255,8 @@ class WebSocketChannel(BaseChannel):
self._conn_chats: dict[Any, set[str]] = {}
# connection -> default chat_id for legacy frames that omit routing.
self._conn_default: dict[Any, str] = {}
# Connections authenticated with a one-time token from /webui/bootstrap.
self._webui_connections: set[Any] = set()
self._stop_event: asyncio.Event | None = None
self._server_task: asyncio.Task[None] | None = None
@@ -284,6 +291,7 @@ class WebSocketChannel(BaseChannel):
if not subs:
self._subs.pop(cid, None)
self._conn_default.pop(connection, None)
self._webui_connections.discard(connection)
async def _maybe_push_active_goal_state(self, chat_id: str) -> None:
"""Replay an active sustained goal from session metadata after *chat_id* is subscribed.
@@ -374,19 +382,25 @@ class WebSocketChannel(BaseChannel):
if static_token:
if supplied and hmac.compare_digest(supplied, static_token):
return None
if supplied and self._tokens.take_issued_token_if_valid(supplied):
if supplied and self._consume_issued_token(connection, supplied):
return None
return connection.respond(401, "Unauthorized")
if self.config.websocket_requires_token:
if supplied and self._tokens.take_issued_token_if_valid(supplied):
if supplied and self._consume_issued_token(connection, supplied):
return None
return connection.respond(401, "Unauthorized")
if supplied:
self._tokens.take_issued_token_if_valid(supplied)
self._consume_issued_token(connection, supplied)
return None
def _consume_issued_token(self, connection: Any, token: str) -> bool:
audience = self._tokens.take_issued_token_audience(token)
if audience == "webui":
self._webui_connections.add(connection)
return audience is not None
# -- Server lifecycle and connection ingress ---------------------------
async def start(self) -> None:
@@ -696,6 +710,12 @@ class WebSocketChannel(BaseChannel):
cli_apps=cli_apps or None,
mcp_presets=mcp_presets or None,
)
if metadata.get("webui") is True and connection in self._webui_connections:
quote = webui_quote_runtime_context({
WEBUI_QUOTE_METADATA: envelope.get("quoted_context"),
})
if quote is not None:
metadata[RUNTIME_CONTEXT_INPUT_META] = [quote]
await self._handle_message(
sender_id=client_id,
chat_id=cid,
@@ -747,6 +767,7 @@ class WebSocketChannel(BaseChannel):
self._subs.clear()
self._conn_chats.clear()
self._conn_default.clear()
self._webui_connections.clear()
self._tokens.clear()
async def _safe_send_to(self, connection: Any, raw: str, *, label: str = "") -> None:
@@ -988,6 +1009,8 @@ class WebSocketChannel(BaseChannel):
self._stream_text_buffers.setdefault(stream_key, []).append(delta)
if stream_id is not None:
body["stream_id"] = stream_id
if stream_end and resuming:
body["resuming"] = True
self._transcripts.prepare_and_append(
chat_id,
body,