chore(release): prepare v0.2.2

This commit is contained in:
Xubin Ren
2026-06-22 20:53:28 +08:00
parent 7170761e47
commit 83c29292d3
11 changed files with 53 additions and 49 deletions
+2 -1
View File
@@ -56,6 +56,7 @@
## 📢 News ## 📢 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-19** 🔎 Firecrawl app, OpenAI image edits, safer session deletion.
- **2026-06-18** 💬 Feishu recovery, Keenable search, Mistral polish, workspace-aware git. - **2026-06-18** 💬 Feishu recovery, Keenable search, Mistral polish, workspace-aware git.
- **2026-06-17** 🧠 Default idle auto-compact, clearer `/dream`, macOS installer fixes. - **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-13** 🗓️ Session-bound automations, sturdier WhatsApp, faster WebUI startup.
- **2026-06-12** 💬 Slack allowlisted channels can require mentions. - **2026-06-12** 💬 Slack allowlisted channels can require mentions.
- **2026-06-11** ✂️ Fenced-code message splitting. - **2026-06-11** ✂️ Fenced-code message splitting.
- **2026-06-10** 📜 Segmented transcripts, Exa/Bocha search, StepFun/SiliconFlow ASR.
<details> <details>
<summary>Earlier news</summary> <summary>Earlier news</summary>
- **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-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-08** 🧮 Token heatmap fix, safer MCP HTTP probing, docs cleanup.
- **2026-06-06** 🧰 SDK MCP cleanup, removable OpenAI image defaults. - **2026-06-06** 🧰 SDK MCP cleanup, removable OpenAI image defaults.
+1 -1
View File
@@ -22,7 +22,7 @@ def _resolve_version() -> str:
return _pkg_version("nanobot-ai") return _pkg_version("nanobot-ai")
except PackageNotFoundError: except PackageNotFoundError:
# Source checkouts often import nanobot without installed dist-info. # 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() __version__ = _resolve_version()
+8 -2
View File
@@ -8,10 +8,16 @@ from typing import Any
from pydantic import Field from pydantic import Field
from nanobot.agent.tools.base import Tool, tool_parameters from nanobot.agent.tools.base import Tool, tool_parameters
from nanobot.agent.tools.schema import ArraySchema, BooleanSchema, IntegerSchema, StringSchema, tool_parameters_schema from nanobot.agent.tools.schema import (
from nanobot.security.workspace_access import current_tool_workspace ArraySchema,
BooleanSchema,
IntegerSchema,
StringSchema,
tool_parameters_schema,
)
from nanobot.apps.cli import CliAppError, CliAppManager, CliAppsRuntimeConfig from nanobot.apps.cli import CliAppError, CliAppManager, CliAppsRuntimeConfig
from nanobot.config_base import Base from nanobot.config_base import Base
from nanobot.security.workspace_access import current_tool_workspace
class CliAppsToolConfig(Base): class CliAppsToolConfig(Base):
+1 -1
View File
@@ -14,7 +14,6 @@ from nanobot.agent.tools.schema import (
StringSchema, StringSchema,
tool_parameters_schema, tool_parameters_schema,
) )
from nanobot.security.workspace_access import current_tool_workspace
from nanobot.config.paths import get_media_dir from nanobot.config.paths import get_media_dir
from nanobot.config_base import Base from nanobot.config_base import Base
from nanobot.providers.image_generation import ( from nanobot.providers.image_generation import (
@@ -22,6 +21,7 @@ from nanobot.providers.image_generation import (
ImageGenerationProvider, ImageGenerationProvider,
get_image_gen_provider, 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.security.workspace_policy import WorkspaceBoundaryError, resolve_allowed_path
from nanobot.utils.artifacts import ( from nanobot.utils.artifacts import (
ArtifactError, ArtifactError,
+1 -1
View File
@@ -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.context import ContextAware, RequestContext
from nanobot.agent.tools.path_utils import resolve_workspace_path from nanobot.agent.tools.path_utils import resolve_workspace_path
from nanobot.agent.tools.schema import ArraySchema, StringSchema, tool_parameters_schema 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.bus.events import OutboundMessage
from nanobot.config.paths import get_workspace_path from nanobot.config.paths import get_workspace_path
from nanobot.security.workspace_access import current_tool_workspace
@tool_parameters( @tool_parameters(
+1 -1
View File
@@ -11,13 +11,13 @@ from datetime import datetime
from typing import Any from typing import Any
import httpx import httpx
from pydantic import Field
from nanobot.bus.events import OutboundMessage from nanobot.bus.events import OutboundMessage
from nanobot.bus.queue import MessageBus from nanobot.bus.queue import MessageBus
from nanobot.channels.base import BaseChannel from nanobot.channels.base import BaseChannel
from nanobot.config.paths import get_runtime_subdir from nanobot.config.paths import get_runtime_subdir
from nanobot.config.schema import Base from nanobot.config.schema import Base
from pydantic import Field
try: try:
import socketio import socketio
+1 -1
View File
@@ -7,12 +7,12 @@ from nanobot.config.paths import (
get_cron_dir, get_cron_dir,
get_data_dir, get_data_dir,
get_legacy_sessions_dir, get_legacy_sessions_dir,
is_default_workspace,
get_logs_dir, get_logs_dir,
get_media_dir, get_media_dir,
get_runtime_subdir, get_runtime_subdir,
get_webui_dir, get_webui_dir,
get_workspace_path, get_workspace_path,
is_default_workspace,
) )
from nanobot.config.schema import Config from nanobot.config.schema import Config
+1 -1
View File
@@ -32,8 +32,8 @@ if TYPE_CHECKING:
from nanobot.providers.azure_openai_provider import AzureOpenAIProvider from nanobot.providers.azure_openai_provider import AzureOpenAIProvider
from nanobot.providers.bedrock_provider import BedrockProvider from nanobot.providers.bedrock_provider import BedrockProvider
from nanobot.providers.github_copilot_provider import GitHubCopilotProvider 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_codex_provider import OpenAICodexProvider
from nanobot.providers.openai_compat_provider import OpenAICompatProvider
def __getattr__(name: str): def __getattr__(name: str):
@@ -78,7 +78,7 @@ def package_skill(skill_path, output_dir=None):
skill_filename = output_path / f"{skill_name}.skill" 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 = [] files_to_package = []
resolved_archive = skill_filename.resolve() resolved_archive = skill_filename.resolve()
@@ -91,7 +91,7 @@ def package_skill(skill_path, output_dir=None):
return None return None
rel_parts = file_path.relative_to(skill_path).parts 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 continue
if file_path.is_file(): if file_path.is_file():
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "nanobot-ai" name = "nanobot-ai"
version = "0.2.1" version = "0.2.2"
description = "A lightweight personal AI assistant framework" description = "A lightweight personal AI assistant framework"
readme = { file = "README.md", content-type = "text/markdown" } readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11" requires-python = ">=3.11"
+34 -37
View File
@@ -1102,12 +1102,7 @@ describe("ThreadShell", () => {
it("scrolls to the bottom after loading a session from the blank new-chat page", async () => { it("scrolls to the bottom after loading a session from the blank new-chat page", async () => {
const client = makeClient(); const client = makeClient();
const scrollIntoView = vi.fn();
const scrollTo = 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( vi.stubGlobal(
"fetch", "fetch",
vi.fn(async (input: RequestInfo | URL) => { vi.fn(async (input: RequestInfo | URL) => {
@@ -1128,47 +1123,49 @@ describe("ThreadShell", () => {
}), }),
); );
try { const { container, rerender } = render(
const { rerender } = render( wrap(
client,
<ThreadShell
session={null}
title="nanobot"
onToggleSidebar={() => {}}
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( wrap(
client, client,
<ThreadShell <ThreadShell
session={null} session={session("chat-a")}
title="nanobot" title="Chat chat-a"
onToggleSidebar={() => {}} onToggleSidebar={() => {}}
onNewChat={() => {}} onNewChat={() => {}}
/>, />,
), ),
); );
});
expect(screen.getByText(HERO_GREETING_PATTERN)).toBeInTheDocument(); await waitFor(() => expect(screen.getByText("loaded answer")).toBeInTheDocument());
scrollIntoView.mockClear(); await waitFor(() =>
expect(scrollTo).toHaveBeenCalledWith({
await act(async () => { top: 1800,
rerender( behavior: "auto",
wrap( }),
client, );
<ThreadShell
session={session("chat-a")}
title="Chat chat-a"
onToggleSidebar={() => {}}
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;
}
}); });
it("opens slash commands on the blank welcome page", async () => { it("opens slash commands on the blank welcome page", async () => {