diff --git a/nanobot/channels/discord/tests/test_discord_channel.py b/nanobot/channels/discord/tests/test_discord_channel.py
index a749363b0..af29ad0ae 100644
--- a/nanobot/channels/discord/tests/test_discord_channel.py
+++ b/nanobot/channels/discord/tests/test_discord_channel.py
@@ -145,14 +145,9 @@ class _FakeChannel:
class _FakeInteractionResponse:
def __init__(self) -> None:
self.messages: list[dict] = []
- self._done = False
async def send_message(self, content: str, *, ephemeral: bool = False) -> None:
self.messages.append({"content": content, "ephemeral": ephemeral})
- self._done = True
-
- def is_done(self) -> bool:
- return self._done
def _make_interaction(
diff --git a/nanobot/channels/mattermost/runtime.py b/nanobot/channels/mattermost/runtime.py
index 048d67158..eb6b9e53f 100644
--- a/nanobot/channels/mattermost/runtime.py
+++ b/nanobot/channels/mattermost/runtime.py
@@ -53,7 +53,6 @@ class MattermostConfig(Base):
include_thread_context: bool = True
thread_context_limit: int = 20
streaming: bool = True
- streaming_max_chars: int = 16000
react_emoji: str = "eyes"
done_emoji: str = "white_check_mark"
send_progress: bool = True
@@ -106,7 +105,6 @@ class MattermostChannel(BaseChannel):
self._ws_task: asyncio.Task[None] | None = None
self._self_id: str | None = None
self._self_username: str | None = None
- self._self_email: str | None = None
self._usernames: dict[str, str] = {}
self._user_emails: dict[str, str] = {}
self._channel_types: dict[str, str] = {}
@@ -138,7 +136,6 @@ class MattermostChannel(BaseChannel):
me = cast(dict[str, Any], resp.json())
self._self_id = me.get("id")
self._self_username = me.get("username")
- self._self_email = me.get("email", "")
self.logger.info("bot @{} connected", self._self_username)
except Exception as e:
self.logger.error("Failed to identify bot user: {}", e)
diff --git a/nanobot/channels/mattermost/tests/test_mattermost_channel.py b/nanobot/channels/mattermost/tests/test_mattermost_channel.py
index 3ca99738d..b44d9c6ad 100644
--- a/nanobot/channels/mattermost/tests/test_mattermost_channel.py
+++ b/nanobot/channels/mattermost/tests/test_mattermost_channel.py
@@ -31,8 +31,6 @@ class _FakeHTTPClient:
self.delete_calls: list[dict[str, Any]] = []
self._get_responses: dict[str, Any] = {}
self._post_responses: dict[str, Any] = {}
- self._put_responses: dict[str, Any] = {}
- self._delete_status: int | None = None
def _req(self, method: str, path: str) -> httpx.Request:
return httpx.Request(method, f"https://chat.example.com{path}")
@@ -46,12 +44,6 @@ class _FakeHTTPClient:
def set_post_response(self, path: str, data: Any) -> None:
self._post_responses[path] = data
- def set_put_response(self, path: str, data: Any) -> None:
- self._put_responses[path] = data
-
- def set_delete_status(self, status: int) -> None:
- self._delete_status = status
-
async def get(self, path: str, **kwargs) -> httpx.Response:
self.get_calls.append({"path": path, **kwargs})
data = self._get_responses.get(path, {"id": "resp_" + path.split("/")[-1]})
@@ -71,13 +63,11 @@ class _FakeHTTPClient:
async def put(self, path: str, *, json: dict[str, Any] | None = None, **kwargs) -> httpx.Response:
self.put_calls.append({"path": path, "json": json})
- data = self._put_responses.get(path, {"id": path.split("/")[-1]})
- return self._resp(200, data, "PUT", path)
+ return self._resp(200, {"id": path.split("/")[-1]}, "PUT", path)
async def delete(self, path: str, **kwargs) -> httpx.Response:
self.delete_calls.append({"path": path})
- status = self._delete_status if self._delete_status is not None else 200
- return self._resp(status, {}, "DELETE", path)
+ return self._resp(200, {}, "DELETE", path)
async def aclose(self) -> None:
pass
@@ -119,7 +109,6 @@ def test_config_defaults():
assert config.server_url == ""
assert config.token == ""
assert config.streaming is True
- assert config.streaming_max_chars == 16000
assert config.send_tool_hints is True
assert config.dm.enabled is True
assert config.dm.policy == "open"
@@ -150,7 +139,6 @@ def test_config_camelcase_aliases():
"serverUrl": "https://mm.example.com",
"token": "abc123",
"allowFromMatchMode": "username",
- "streamingMaxChars": 8000,
"replyInThread": False,
"sendToolHints": False,
}
@@ -158,7 +146,6 @@ def test_config_camelcase_aliases():
assert config.server_url == "https://mm.example.com"
assert config.token == "abc123"
assert config.allow_from_match_mode == "username"
- assert config.streaming_max_chars == 8000
assert config.reply_in_thread is False
assert config.send_tool_hints is False
@@ -194,7 +181,6 @@ async def test_start_identifies_bot():
assert channel._self_id == "botuserid123"
assert channel._self_username == "nanobot"
- assert channel._self_email == "bot@example.com"
assert not start_task.done()
user_me_calls = [c for c in fake.get_calls[calls_before:] if "/api/v4/users/me" in c["path"]]
assert len(user_me_calls) == 1
diff --git a/nanobot/channels/mochat/runtime.py b/nanobot/channels/mochat/runtime.py
index e5e44e863..b91828000 100644
--- a/nanobot/channels/mochat/runtime.py
+++ b/nanobot/channels/mochat/runtime.py
@@ -277,7 +277,7 @@ class MochatChannel(BaseChannel):
self.config: MochatConfig = config
self._http: httpx.AsyncClient | None = None
self._socket: Any = None
- self._ws_connected = self._ws_ready = False
+ self._ws_ready = False
self._state_dir = get_runtime_subdir("mochat")
self._cursor_path = self._state_dir / "session_cursors.json"
@@ -346,7 +346,7 @@ class MochatChannel(BaseChannel):
if self._http:
await self._http.aclose()
self._http = None
- self._ws_connected = self._ws_ready = False
+ self._ws_ready = False
async def send(self, msg: OutboundMessage) -> None:
"""Send outbound message to session or panel."""
@@ -422,7 +422,7 @@ class MochatChannel(BaseChannel):
)
async def connect() -> None:
- self._ws_connected, self._ws_ready = True, False
+ self._ws_ready = False
self.logger.info("websocket connected")
subscribed = await self._subscribe_all()
self._ws_ready = subscribed
@@ -431,7 +431,7 @@ class MochatChannel(BaseChannel):
async def disconnect() -> None:
if not self._running:
return
- self._ws_connected = self._ws_ready = False
+ self._ws_ready = False
self.logger.warning("websocket disconnected")
await self._ensure_fallback_workers()
diff --git a/nanobot/channels/signal/tests/test_signal_markdown.py b/nanobot/channels/signal/tests/test_signal_markdown.py
index 7cb62a282..2ab8d4cce 100644
--- a/nanobot/channels/signal/tests/test_signal_markdown.py
+++ b/nanobot/channels/signal/tests/test_signal_markdown.py
@@ -363,13 +363,6 @@ def test_reported_daily_brief_pattern():
# ---------------------------------------------------------------------------
-def _resolve_chunk_styles(text: str, max_len: int) -> tuple[list[str], list[list[str]]]:
- """Helper: full markdown → signal pipeline, including chunking."""
- plain, styles = _markdown_to_signal(text)
- chunks = split_message(plain, max_len) if plain else [""]
- return chunks, _partition_styles(plain, chunks, styles)
-
-
def test_partition_styles_single_chunk_passthrough():
plain, styles = _markdown_to_signal("**bold** plain *it*")
parts = _partition_styles(plain, [plain], styles)
diff --git a/nanobot/channels/slack/runtime.py b/nanobot/channels/slack/runtime.py
index ff000c25f..eb81a9d6b 100644
--- a/nanobot/channels/slack/runtime.py
+++ b/nanobot/channels/slack/runtime.py
@@ -69,7 +69,6 @@ class SlackConfig(Base):
webhook_path: str = "/slack/events"
bot_token: str = ""
app_token: str = ""
- user_token_read_only: bool = True
reply_in_thread: bool = True
react_emoji: str = "eyes"
done_emoji: str = "white_check_mark"
diff --git a/nanobot/channels/websocket/tests/test_websocket_http_routes.py b/nanobot/channels/websocket/tests/test_websocket_http_routes.py
index aba12221b..5e1566cc2 100644
--- a/nanobot/channels/websocket/tests/test_websocket_http_routes.py
+++ b/nanobot/channels/websocket/tests/test_websocket_http_routes.py
@@ -1244,39 +1244,6 @@ async def test_pairing_routes_require_token_and_approve_or_deny(
assert "Missing pairing code" in missing_code.text
-def test_api_service_settings_read_api_key_from_webui_payload(bus: MagicMock) -> None:
- channel = _ch(bus)
- request = _FakeReq(path="/api/settings/api-service/start")
- setattr(
- request,
- "_nanobot_webui_mutation_payload",
- {"host": "0.0.0.0", "port": 8900, "timeout": 120, "api_key": "secret-token"},
- )
-
- query = channel.gateway.http.settings_routes._parse_api_service_settings_query(request)
-
- assert query == {
- "host": ["0.0.0.0"],
- "port": ["8900"],
- "timeout": ["120"],
- "api_key": ["secret-token"],
- }
-
-
-def test_api_service_settings_reject_non_string_api_key(bus: MagicMock) -> None:
- from nanobot.webui.settings_api import WebUISettingsError
-
- channel = _ch(bus)
- request = _FakeReq(path="/api/settings/api-service/start")
- setattr(
- request,
- "_nanobot_webui_mutation_payload",
- {"host": "127.0.0.1", "api_key": 123},
- )
-
- with pytest.raises(WebUISettingsError, match="API key must be a string"):
- channel.gateway.http.settings_routes._parse_api_service_settings_query(request)
-
@pytest.mark.asyncio
async def test_nanobot_feature_remote_install_requires_opt_in(
bus: MagicMock,
diff --git a/nanobot/channels/websocket/tests/ws_test_client.py b/nanobot/channels/websocket/tests/ws_test_client.py
index ae3fbc490..d5e183e37 100644
--- a/nanobot/channels/websocket/tests/ws_test_client.py
+++ b/nanobot/channels/websocket/tests/ws_test_client.py
@@ -202,12 +202,6 @@ class WsTestClient:
assert msg.event == "delta", f"Expected 'delta' event, got '{msg.event}'"
return msg
- async def recv_stream_end(self, timeout: float = 10.0) -> WsMessage:
- """Receive and validate a 'stream_end' event."""
- msg = await self.recv(timeout)
- assert msg.event == "stream_end", f"Expected 'stream_end' event, got '{msg.event}'"
- return msg
-
async def collect_stream(self, timeout: float = 10.0) -> list[WsMessage]:
"""Collect all deltas and the final stream_end into a list."""
messages: list[WsMessage] = []
@@ -232,10 +226,6 @@ class WsTestClient:
"""Send a JSON frame."""
await self.ws.send(json.dumps(data, ensure_ascii=False))
- async def send_content(self, content: str) -> None:
- """Send content in the preferred JSON format ``{"content": ...}``."""
- await self.send_json({"content": content})
-
# -- Connection introspection -----------------------------------------
@property
diff --git a/nanobot/process_runtime.py b/nanobot/process_runtime.py
index 359fed8a6..c5e425511 100644
--- a/nanobot/process_runtime.py
+++ b/nanobot/process_runtime.py
@@ -96,22 +96,6 @@ class ManagedProcessRuntime(Generic[_StartOptionsT]):
# it; poll() both reaps it and reports the real lifecycle state.
self._owned_process: Any | None = None
- @classmethod
- def refresh_state_pid(cls, *, paths: ProcessRuntimePaths) -> None:
- """Update a managed state file after the recorded process restarts."""
- if not paths.state_path.exists():
- return
- try:
- state = json.loads(paths.state_path.read_text(encoding="utf-8"))
- except (json.JSONDecodeError, OSError):
- return
- state["pid"] = os.getpid()
- runtime = cls(paths=paths)
- state.pop("stable_identity", None)
- state.update(runtime.process_identity_record(os.getpid()))
- state["started_at"] = _utc_now()
- runtime._write_state(state)
-
def start_background(self, options: _StartOptionsT) -> ProcessResult:
"""Start the configured command as a detached process."""
with self._lifecycle_lock():
diff --git a/nanobot/session/webui_turns.py b/nanobot/session/webui_turns.py
index 6bb719096..8085f78ce 100644
--- a/nanobot/session/webui_turns.py
+++ b/nanobot/session/webui_turns.py
@@ -685,15 +685,6 @@ class WebuiTurnCoordinator:
)
)
- async def publish_run_status(
- self,
- msg: InboundMessage,
- status: str,
- *,
- started_at: float | None = None,
- ) -> None:
- await publish_turn_run_status(self.bus, msg, status, started_at=started_at)
-
async def handle_turn_end(
self,
msg: InboundMessage,
diff --git a/nanobot/webui/gateway_tokens.py b/nanobot/webui/gateway_tokens.py
index 0b3d9d3d7..22fe46cc7 100644
--- a/nanobot/webui/gateway_tokens.py
+++ b/nanobot/webui/gateway_tokens.py
@@ -63,9 +63,6 @@ class GatewayTokenStore:
self.api_tokens[token_value] = expiry
return token_value
- def take_issued_token_if_valid(self, token_value: str | None) -> bool:
- return self.take_issued_token_audience(token_value) is not None
-
def take_issued_token_audience(
self,
token_value: str | None,
diff --git a/nanobot/webui/settings_routes.py b/nanobot/webui/settings_routes.py
index c938ae106..25f79ccdf 100644
--- a/nanobot/webui/settings_routes.py
+++ b/nanobot/webui/settings_routes.py
@@ -36,7 +36,6 @@ from nanobot.webui.nanobot_features_api import (
nanobot_features_payload,
)
from nanobot.webui.settings_api import (
- WebUISettingsError,
complete_oauth_provider,
create_model_configuration,
create_provider_settings,
@@ -490,17 +489,6 @@ class WebUISettingsRouter:
lambda: request_image_generation_reload(self.bus),
)
- async def _apply_image_generation_runtime_change(
- self,
- payload: dict[str, Any],
- ) -> dict[str, Any]:
- updated, restart_cleared = (
- await self._apply_image_generation_runtime_change_result(payload)
- )
- if restart_cleared:
- self._restart_sections.discard("image")
- return updated
-
async def _reload_mcp_runtime(self) -> dict[str, Any]:
if self._mcp_reload is None:
return {
@@ -531,47 +519,9 @@ class WebUISettingsRouter:
def _parse_mcp_settings_query(self, request: WsRequest) -> QueryParams:
return self._query(request)
- def _parse_provider_settings_query(self, request: WsRequest) -> QueryParams:
- return self._query(request)
-
- def _parse_api_service_settings_query(self, request: WsRequest) -> QueryParams:
- payload = _mutation_payload(request)
- if payload is not None:
- api_key = payload.get("api_key")
- if api_key is not None and not isinstance(api_key, str):
- raise WebUISettingsError("API service API key must be a string")
- return self._query(request)
-
def _api_runtime(self) -> ApiRuntime:
return ApiRuntime(paths=api_runtime_paths(self.settings.config.path))
- def _api_service_payload(
- self,
- *,
- last_action: str | None = None,
- ) -> dict[str, Any]:
- return capability_domain.api_service_payload(
- self.settings,
- self._api_runtime(),
- last_action=last_action,
- )
-
- @staticmethod
- def _masked_secret(value: str) -> str | None:
- return capability_domain.masked_api_secret(value)
-
- @staticmethod
- def _api_runtime_message(message: str) -> str:
- return capability_domain.api_runtime_message(message)
-
- def _parse_channel_values(self, request: WsRequest) -> dict[str, Any]:
- return self._system.parse_channel_values(
- SettingsRequest(
- query=self._query(request),
- payload=_mutation_payload(request),
- )
- )
-
def _save_channel_config_values(
self,
name: str,
@@ -610,17 +560,6 @@ class WebUISettingsRouter:
allow_install=allow_install,
)
- @staticmethod
- def _feature_runtime_fallback(
- payload: dict[str, Any],
- *,
- message: str,
- ) -> dict[str, Any]:
- return system_domain.SystemSettingsHandler.feature_runtime_fallback(
- payload,
- message=message,
- )
-
def _allow_feature_package_install(
self,
connection: Any,
diff --git a/pyproject.toml b/pyproject.toml
index f0bf5fa46..c7f75d1f8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,6 @@ dependencies = [
"pydantic-settings>=2.12.0,<3.0.0",
# Feishu's lark-oapi currently requires websockets<16; core supports 15 and 16.
"websockets>=15.0,<17.0",
- "websocket-client>=1.9.0,<2.0.0",
"httpx[socks]>=0.28.0,<1.0.0",
"ddgs>=9.5.5,<10.0.0",
"oauth-cli-kit>=0.1.6,<1.0.0",
diff --git a/tui/src/command-menu.ts b/tui/src/command-menu.ts
index 2b13c94ef..31e403318 100644
--- a/tui/src/command-menu.ts
+++ b/tui/src/command-menu.ts
@@ -5,7 +5,7 @@ import { PickerMenu, type PickerMenuTheme } from "./picker-menu"
export type CommandMenuTheme = PickerMenuTheme
-export type TuiCommandAction =
+type TuiCommandAction =
| "sessions"
| "new-chat"
| "context"
diff --git a/tui/src/protocol.ts b/tui/src/protocol.ts
index 0f190a040..abd4cb2f6 100644
--- a/tui/src/protocol.ts
+++ b/tui/src/protocol.ts
@@ -29,14 +29,14 @@ export interface FileEditEvent {
diff?: FileDiff
}
-export interface FileDiff {
+interface FileDiff {
format: "unified" | string
context?: number
truncated?: boolean
text?: string
}
-export interface MediaAttachment {
+interface MediaAttachment {
kind: "image" | "video" | "file"
url: string
name?: string
@@ -225,7 +225,7 @@ export interface SessionContextSnapshot {
lastUsage: TokenUsage | null
}
-export interface SessionMention {
+interface SessionMention {
name: string
session_key: string
title?: string
diff --git a/webui/src/channel-plugins/i18n.ts b/webui/src/channel-plugins/i18n.ts
index 95b8a2b51..10984e1da 100644
--- a/webui/src/channel-plugins/i18n.ts
+++ b/webui/src/channel-plugins/i18n.ts
@@ -1,6 +1,6 @@
import type { TFunction } from "i18next";
-export type ChannelFieldMessages = {
+type ChannelFieldMessages = {
label: string;
placeholder?: string;
help?: string;
diff --git a/webui/src/components/CliAppMentionText.tsx b/webui/src/components/CliAppMentionText.tsx
index 1abfb89b3..57e1e7acc 100644
--- a/webui/src/components/CliAppMentionText.tsx
+++ b/webui/src/components/CliAppMentionText.tsx
@@ -137,7 +137,7 @@ export function CapabilityMentionToken({
return ;
}
-export function SessionMentionToken({
+function SessionMentionToken({
mention,
label,
variant,
@@ -172,7 +172,7 @@ export function SessionMentionToken({
);
}
-export function CliAppMentionToken({
+function CliAppMentionToken({
app,
label,
variant,
@@ -229,7 +229,7 @@ export function CliAppMentionToken({
);
}
-export function McpPresetMentionToken({
+function McpPresetMentionToken({
preset,
label,
variant,
diff --git a/webui/src/components/FileReferenceChip.tsx b/webui/src/components/FileReferenceChip.tsx
index 80a63d592..9ce0e040a 100644
--- a/webui/src/components/FileReferenceChip.tsx
+++ b/webui/src/components/FileReferenceChip.tsx
@@ -151,7 +151,7 @@ export function splitFilePath(path: string): { directory: string; name: string }
};
}
-export function fileKindForPath(path: string): FileReferenceKind {
+function fileKindForPath(path: string): FileReferenceKind {
const normalized = path.toLowerCase();
const name = normalized.split(/[\\/]/).pop() ?? normalized;
const ext = name.includes(".") ? name.split(".").pop() ?? "" : "";
@@ -193,7 +193,7 @@ export function fileKindForPath(path: string): FileReferenceKind {
}
}
-export function FileReferenceIcon({ kind }: { kind: FileReferenceKind }) {
+function FileReferenceIcon({ kind }: { kind: FileReferenceKind }) {
if (kind === "python") {
return (