From 83c29292d33dc15d5b4a6893c534abbfb3f0c450 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Mon, 22 Jun 2026 10:57:29 +0800 Subject: [PATCH] chore(release): prepare v0.2.2 --- README.md | 3 +- nanobot/__init__.py | 2 +- nanobot/agent/tools/cli_apps.py | 10 ++- nanobot/agent/tools/image_generation.py | 2 +- nanobot/agent/tools/message.py | 2 +- nanobot/channels/mochat.py | 2 +- nanobot/config/__init__.py | 2 +- nanobot/providers/__init__.py | 2 +- .../skill-creator/scripts/package_skill.py | 4 +- pyproject.toml | 2 +- webui/src/tests/thread-shell.test.tsx | 71 +++++++++---------- 11 files changed, 53 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 2bbf123f..491fc1f6 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ ## 📢 News +- **2026-06-22** 🚀 Released **v0.2.2** — **The SDK & WebUI Durability Release** makes the Python SDK a first-class runtime, adds segmented WebUI transcripts, automation management, richer search/STT providers, token usage visibility, and stronger session/provider reliability. Please see [release notes](https://github.com/HKUDS/nanobot/releases/tag/v0.2.2) for details. - **2026-06-19** 🔎 Firecrawl app, OpenAI image edits, safer session deletion. - **2026-06-18** 💬 Feishu recovery, Keenable search, Mistral polish, workspace-aware git. - **2026-06-17** 🧠 Default idle auto-compact, clearer `/dream`, macOS installer fixes. @@ -65,11 +66,11 @@ - **2026-06-13** 🗓️ Session-bound automations, sturdier WhatsApp, faster WebUI startup. - **2026-06-12** 💬 Slack allowlisted channels can require mentions. - **2026-06-11** ✂️ Fenced-code message splitting. -- **2026-06-10** 📜 Segmented transcripts, Exa/Bocha search, StepFun/SiliconFlow ASR.
Earlier news +- **2026-06-10** 📜 Segmented transcripts, Exa/Bocha search, StepFun/SiliconFlow ASR. - **2026-06-09** 🎙️ Shared voice input, more STT providers, TeX and email polish. - **2026-06-08** 🧮 Token heatmap fix, safer MCP HTTP probing, docs cleanup. - **2026-06-06** 🧰 SDK MCP cleanup, removable OpenAI image defaults. diff --git a/nanobot/__init__.py b/nanobot/__init__.py index 84b09532..5805a5b1 100644 --- a/nanobot/__init__.py +++ b/nanobot/__init__.py @@ -22,7 +22,7 @@ def _resolve_version() -> str: return _pkg_version("nanobot-ai") except PackageNotFoundError: # Source checkouts often import nanobot without installed dist-info. - return _read_pyproject_version() or "0.2.1" + return _read_pyproject_version() or "0.2.2" __version__ = _resolve_version() diff --git a/nanobot/agent/tools/cli_apps.py b/nanobot/agent/tools/cli_apps.py index e794c09d..4fec51e1 100644 --- a/nanobot/agent/tools/cli_apps.py +++ b/nanobot/agent/tools/cli_apps.py @@ -8,10 +8,16 @@ from typing import Any from pydantic import Field from nanobot.agent.tools.base import Tool, tool_parameters -from nanobot.agent.tools.schema import ArraySchema, BooleanSchema, IntegerSchema, StringSchema, tool_parameters_schema -from nanobot.security.workspace_access import current_tool_workspace +from nanobot.agent.tools.schema import ( + ArraySchema, + BooleanSchema, + IntegerSchema, + StringSchema, + tool_parameters_schema, +) from nanobot.apps.cli import CliAppError, CliAppManager, CliAppsRuntimeConfig from nanobot.config_base import Base +from nanobot.security.workspace_access import current_tool_workspace class CliAppsToolConfig(Base): diff --git a/nanobot/agent/tools/image_generation.py b/nanobot/agent/tools/image_generation.py index 82a09fb9..481d91f2 100644 --- a/nanobot/agent/tools/image_generation.py +++ b/nanobot/agent/tools/image_generation.py @@ -14,7 +14,6 @@ from nanobot.agent.tools.schema import ( StringSchema, tool_parameters_schema, ) -from nanobot.security.workspace_access import current_tool_workspace from nanobot.config.paths import get_media_dir from nanobot.config_base import Base from nanobot.providers.image_generation import ( @@ -22,6 +21,7 @@ from nanobot.providers.image_generation import ( ImageGenerationProvider, get_image_gen_provider, ) +from nanobot.security.workspace_access import current_tool_workspace from nanobot.security.workspace_policy import WorkspaceBoundaryError, resolve_allowed_path from nanobot.utils.artifacts import ( ArtifactError, diff --git a/nanobot/agent/tools/message.py b/nanobot/agent/tools/message.py index 5b778480..d7d5052e 100644 --- a/nanobot/agent/tools/message.py +++ b/nanobot/agent/tools/message.py @@ -10,9 +10,9 @@ from nanobot.agent.tools.base import Tool, tool_parameters from nanobot.agent.tools.context import ContextAware, RequestContext from nanobot.agent.tools.path_utils import resolve_workspace_path from nanobot.agent.tools.schema import ArraySchema, StringSchema, tool_parameters_schema -from nanobot.security.workspace_access import current_tool_workspace from nanobot.bus.events import OutboundMessage from nanobot.config.paths import get_workspace_path +from nanobot.security.workspace_access import current_tool_workspace @tool_parameters( diff --git a/nanobot/channels/mochat.py b/nanobot/channels/mochat.py index dfe22564..de99711f 100644 --- a/nanobot/channels/mochat.py +++ b/nanobot/channels/mochat.py @@ -11,13 +11,13 @@ from datetime import datetime from typing import Any import httpx +from pydantic import Field from nanobot.bus.events import OutboundMessage from nanobot.bus.queue import MessageBus from nanobot.channels.base import BaseChannel from nanobot.config.paths import get_runtime_subdir from nanobot.config.schema import Base -from pydantic import Field try: import socketio diff --git a/nanobot/config/__init__.py b/nanobot/config/__init__.py index 386d9857..18d5c7fc 100644 --- a/nanobot/config/__init__.py +++ b/nanobot/config/__init__.py @@ -7,12 +7,12 @@ from nanobot.config.paths import ( get_cron_dir, get_data_dir, get_legacy_sessions_dir, - is_default_workspace, get_logs_dir, get_media_dir, get_runtime_subdir, get_webui_dir, get_workspace_path, + is_default_workspace, ) from nanobot.config.schema import Config diff --git a/nanobot/providers/__init__.py b/nanobot/providers/__init__.py index 6150788a..f5834b73 100644 --- a/nanobot/providers/__init__.py +++ b/nanobot/providers/__init__.py @@ -32,8 +32,8 @@ if TYPE_CHECKING: from nanobot.providers.azure_openai_provider import AzureOpenAIProvider from nanobot.providers.bedrock_provider import BedrockProvider from nanobot.providers.github_copilot_provider import GitHubCopilotProvider - from nanobot.providers.openai_compat_provider import OpenAICompatProvider from nanobot.providers.openai_codex_provider import OpenAICodexProvider + from nanobot.providers.openai_compat_provider import OpenAICompatProvider def __getattr__(name: str): diff --git a/nanobot/skills/skill-creator/scripts/package_skill.py b/nanobot/skills/skill-creator/scripts/package_skill.py index 7e3a8f17..1494b10e 100755 --- a/nanobot/skills/skill-creator/scripts/package_skill.py +++ b/nanobot/skills/skill-creator/scripts/package_skill.py @@ -78,7 +78,7 @@ def package_skill(skill_path, output_dir=None): skill_filename = output_path / f"{skill_name}.skill" - EXCLUDED_DIRS = {".git", ".svn", ".hg", "__pycache__", "node_modules"} + excluded_dirs = {".git", ".svn", ".hg", "__pycache__", "node_modules"} files_to_package = [] resolved_archive = skill_filename.resolve() @@ -91,7 +91,7 @@ def package_skill(skill_path, output_dir=None): return None rel_parts = file_path.relative_to(skill_path).parts - if any(part in EXCLUDED_DIRS for part in rel_parts): + if any(part in excluded_dirs for part in rel_parts): continue if file_path.is_file(): diff --git a/pyproject.toml b/pyproject.toml index 915bd8c3..76b0edfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nanobot-ai" -version = "0.2.1" +version = "0.2.2" description = "A lightweight personal AI assistant framework" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/webui/src/tests/thread-shell.test.tsx b/webui/src/tests/thread-shell.test.tsx index 2de4b7b5..42a4f5c2 100644 --- a/webui/src/tests/thread-shell.test.tsx +++ b/webui/src/tests/thread-shell.test.tsx @@ -1102,12 +1102,7 @@ describe("ThreadShell", () => { it("scrolls to the bottom after loading a session from the blank new-chat page", async () => { const client = makeClient(); - const scrollIntoView = vi.fn(); const scrollTo = vi.fn(); - const originalScrollIntoView = HTMLElement.prototype.scrollIntoView; - const originalScrollTo = HTMLElement.prototype.scrollTo; - HTMLElement.prototype.scrollIntoView = scrollIntoView; - HTMLElement.prototype.scrollTo = scrollTo; vi.stubGlobal( "fetch", vi.fn(async (input: RequestInfo | URL) => { @@ -1128,47 +1123,49 @@ describe("ThreadShell", () => { }), ); - try { - const { rerender } = render( + const { container, rerender } = render( + wrap( + client, + {}} + onNewChat={() => {}} + />, + ), + ); + + expect(screen.getByText(HERO_GREETING_PATTERN)).toBeInTheDocument(); + const scroller = container.querySelector(".thread-viewport-scrollbar") as HTMLElement; + Object.defineProperties(scroller, { + scrollHeight: { configurable: true, value: 2400 }, + clientHeight: { configurable: true, value: 600 }, + scrollTop: { configurable: true, writable: true, value: 0 }, + scrollTo: { configurable: true, value: scrollTo }, + }); + scrollTo.mockClear(); + + await act(async () => { + rerender( wrap( client, {}} onNewChat={() => {}} />, ), ); + }); - expect(screen.getByText(HERO_GREETING_PATTERN)).toBeInTheDocument(); - scrollIntoView.mockClear(); - - await act(async () => { - rerender( - wrap( - client, - {}} - onNewChat={() => {}} - />, - ), - ); - }); - - await waitFor(() => expect(screen.getByText("loaded answer")).toBeInTheDocument()); - await waitFor(() => - expect(scrollTo).toHaveBeenCalledWith({ - top: 0, - behavior: "auto", - }), - ); - } finally { - HTMLElement.prototype.scrollIntoView = originalScrollIntoView; - HTMLElement.prototype.scrollTo = originalScrollTo; - } + await waitFor(() => expect(screen.getByText("loaded answer")).toBeInTheDocument()); + await waitFor(() => + expect(scrollTo).toHaveBeenCalledWith({ + top: 1800, + behavior: "auto", + }), + ); }); it("opens slash commands on the blank welcome page", async () => {