From c8996b405f09b77e174bfbb13869dae35d3bbfce Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 28 Jun 2026 16:51:14 +0800 Subject: [PATCH] feat: add ChatUI project workspaces --- astrbot/core/astr_main_agent.py | 94 ++-- astrbot/core/db/__init__.py | 4 + astrbot/core/db/po.py | 4 + astrbot/core/db/sqlite.py | 27 ++ astrbot/core/tools/computer_tools/fs.py | 134 +++++- astrbot/core/tools/computer_tools/python.py | 9 +- astrbot/core/tools/computer_tools/shell.py | 10 +- astrbot/core/tools/computer_tools/util.py | 44 +- astrbot/core/tools/message_tools.py | 26 +- astrbot/core/workspace.py | 195 ++++++++ astrbot/dashboard/schemas.py | 2 + .../services/chatui_project_service.py | 92 +++- .../src/api/generated/openapi-v1/types.gen.ts | 4 + .../mdi-subset/materialdesignicons-subset.css | 6 +- .../materialdesignicons-webfont-subset.woff | Bin 19968 -> 20136 bytes .../materialdesignicons-webfont-subset.woff2 | Bin 16008 -> 16152 bytes dashboard/src/components/chat/Chat.vue | 399 ++++++++++++---- dashboard/src/components/chat/ChatInput.vue | 100 +++- .../src/components/chat/ProjectDialog.vue | 61 ++- dashboard/src/components/chat/ProjectList.vue | 418 ++++++++++++----- dashboard/src/components/chat/ProjectView.vue | 36 +- .../src/components/chat/ReasoningSidebar.vue | 4 +- dashboard/src/components/chat/ThreadPanel.vue | 4 +- .../chat/message_list_comps/RefsSidebar.vue | 15 +- dashboard/src/composables/useProjects.ts | 27 +- .../src/i18n/locales/en-US/features/chat.json | 10 + .../src/i18n/locales/ru-RU/features/chat.json | 10 + .../src/i18n/locales/zh-CN/features/chat.json | 10 + dashboard/src/layouts/full/FullLayout.vue | 7 +- .../full/vertical-header/VerticalHeader.vue | 429 ++++++++++++------ dashboard/src/stores/chatHeader.ts | 19 + dashboard/src/stores/customizer.ts | 4 + openspec/openapi-v1.yaml | 5 + tests/unit/test_chatui_project_service.py | 140 ++++++ tests/unit/test_python_tools.py | 8 +- 35 files changed, 1902 insertions(+), 455 deletions(-) create mode 100644 astrbot/core/workspace.py create mode 100644 dashboard/src/stores/chatHeader.ts create mode 100644 tests/unit/test_chatui_project_service.py diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 16ebac7a8b..1eae0f17e3 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -29,6 +29,7 @@ TOOL_CALL_PROMPT_SKILLS_LIKE_MODE, ) from astrbot.core.conversation_mgr import Conversation +from astrbot.core.db import BaseDatabase from astrbot.core.message.components import File, Image, Record, Reply, Video from astrbot.core.persona_error_reply import ( extract_persona_custom_error_message_from_persona, @@ -73,7 +74,6 @@ RollbackSkillReleaseTool, RunBrowserSkillTool, SyncSkillReleaseTool, - normalize_umo_for_workspace, ) from astrbot.core.tools.cron_tools import FutureTaskTool from astrbot.core.tools.knowledge_base_tools import ( @@ -115,6 +115,10 @@ extract_quoted_message_text, ) from astrbot.core.utils.string_utils import normalize_and_dedupe_strings +from astrbot.core.workspace import ( + normalize_umo_for_workspace, + resolve_workspace_root_for_umo, +) LLM_ERROR_MESSAGE_EXTRA_KEY = "_llm_error_message" WEEKDAY_NAMES = ( @@ -357,41 +361,63 @@ def _apply_prompt_prefix(req: ProviderRequest, cfg: dict) -> None: req.prompt = f"{prefix}{req.prompt}" -def _get_workspace_path_for_umo(umo: str) -> Path: - normalized_umo = normalize_umo_for_workspace(umo) - return Path(get_astrbot_workspaces_path()) / normalized_umo +async def _get_workspace_path_for_umo(umo: str, plugin_context: Context) -> Path: + """Resolve the workspace path for the current request. + + Args: + umo: Unified message origin. + plugin_context: Star context containing the database instance. + + Returns: + Workspace path used as cwd. + """ + fallback_root = ( + Path(get_astrbot_workspaces_path()) / normalize_umo_for_workspace(umo) + ).resolve(strict=False) + db = getattr(plugin_context, "_db", None) + if not isinstance(db, BaseDatabase): + return fallback_root + try: + return await resolve_workspace_root_for_umo(umo, db) + except Exception: + return fallback_root -def _apply_workspace_extra_prompt( +async def _apply_workspace_extra_prompt( event: AstrMessageEvent, req: ProviderRequest, + plugin_context: Context, ) -> None: - extra_prompt_path = _get_workspace_path_for_umo(event.unified_msg_origin) / ( - "EXTRA_PROMPT.md" + workspace_root = await _get_workspace_path_for_umo( + event.unified_msg_origin, + plugin_context, ) - if not extra_prompt_path.is_file(): - return - - try: - extra_prompt = extra_prompt_path.read_text(encoding="utf-8").strip() - except Exception as exc: # noqa: BLE001 - logger.warning( - "Failed to read workspace extra prompt for umo=%s from %s: %s", - event.unified_msg_origin, - extra_prompt_path, - exc, - ) - return + extra_prompts: list[str] = [] + extra_prompt_path = workspace_root / "EXTRA_PROMPT.md" + if extra_prompt_path.is_file(): + try: + extra_prompt = extra_prompt_path.read_text(encoding="utf-8").strip() + except Exception as exc: # noqa: BLE001 + logger.warning( + "Failed to read workspace extra prompt for umo=%s from %s: %s", + event.unified_msg_origin, + extra_prompt_path, + exc, + ) + else: + if extra_prompt: + extra_prompts.append(f"From `{extra_prompt_path}`:\n{extra_prompt}") - if not extra_prompt: + if not extra_prompts: return + extra_prompt_text = "\n\n".join(extra_prompts) req.system_prompt = ( f"{req.system_prompt or ''}\n" "[Workspace Extra Prompt]\n" "The following instructions are loaded from the current workspace " "`EXTRA_PROMPT.md` file.\n" - f"{extra_prompt}\n" + f"{extra_prompt_text}\n" ) @@ -498,13 +524,13 @@ async def _ensure_persona_and_skills( skill_manager = SkillManager() skills = skill_manager.list_skills(active_only=True, runtime=runtime) skills = _filter_skills_for_current_config(skills, cfg) - workspace_skills = ( - skill_manager.list_workspace_skills( - _get_workspace_path_for_umo(event.unified_msg_origin) + workspace_skills: list[SkillInfo] = [] + if runtime == "local": + workspace_root = await _get_workspace_path_for_umo( + event.unified_msg_origin, + plugin_context, ) - if runtime == "local" - else [] - ) + workspace_skills.extend(skill_manager.list_workspace_skills(workspace_root)) if skills or workspace_skills: if persona and persona.get("skills") is not None: @@ -989,7 +1015,7 @@ async def _decorate_llm_request( if tz is None: tz = plugin_context.get_config().get("timezone") _append_system_reminders(event, req, cfg, tz) - _apply_workspace_extra_prompt(event, req) + await _apply_workspace_extra_prompt(event, req, plugin_context) def _plugin_tool_fix(event: AstrMessageEvent, req: ProviderRequest) -> None: @@ -1590,10 +1616,14 @@ async def build_main_agent( ) if config.computer_use_runtime == "local": + workspace_root = await _get_workspace_path_for_umo( + event.unified_msg_origin, + plugin_context, + ) + workspace_prompt = f"\nCurrent workspace you can use: `{workspace_root}`\n" tool_prompt += ( - f"\nCurrent workspace you can use: " - f"`{_get_workspace_path_for_umo(event.unified_msg_origin)}`\n" - "Unless the user explicitly specifies a different directory, " + workspace_prompt + + "Unless the user explicitly specifies a different directory, " "perform all file-related operations in this workspace.\n" ) diff --git a/astrbot/core/db/__init__.py b/astrbot/core/db/__init__.py index cf37c48663..8e319bc529 100644 --- a/astrbot/core/db/__init__.py +++ b/astrbot/core/db/__init__.py @@ -851,6 +851,8 @@ async def create_chatui_project( title: str, emoji: str | None = "📁", description: str | None = None, + workspace_type: str = "session", + workspace_path: str | None = None, ) -> ChatUIProject: """Create a new ChatUI project.""" ... @@ -877,6 +879,8 @@ async def update_chatui_project( title: str | None = None, emoji: str | None = None, description: str | None = None, + workspace_type: str | None = None, + workspace_path: str | None = None, ) -> None: """Update a ChatUI project.""" ... diff --git a/astrbot/core/db/po.py b/astrbot/core/db/po.py index 9a297b34da..5bfbc7d9e6 100644 --- a/astrbot/core/db/po.py +++ b/astrbot/core/db/po.py @@ -447,6 +447,10 @@ class ChatUIProject(TimestampMixin, SQLModel, table=True): """Title of the project""" description: str | None = Field(default=None, max_length=1000) """Description of the project""" + workspace_type: str = Field(default="session", nullable=False, max_length=32) + """Workspace mode: session, project, or custom""" + workspace_path: str | None = Field(default=None, max_length=1024) + """Custom workspace path""" __table_args__ = ( UniqueConstraint( diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index b7706cc513..f59f234192 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -64,6 +64,7 @@ async def initialize(self) -> None: await self._ensure_persona_skills_column(conn) await self._ensure_persona_custom_error_message_column(conn) await self._ensure_platform_message_history_checkpoint_column(conn) + await self._ensure_chatui_project_workspace_columns(conn) await conn.commit() async def _ensure_persona_folder_columns(self, conn) -> None: @@ -128,6 +129,23 @@ async def _ensure_platform_message_history_checkpoint_column(self, conn) -> None ) ) + async def _ensure_chatui_project_workspace_columns(self, conn) -> None: + """Ensure chatui_projects has workspace configuration columns.""" + result = await conn.execute(text("PRAGMA table_info(chatui_projects)")) + columns = {row[1] for row in result.fetchall()} + + if "workspace_type" not in columns: + await conn.execute( + text( + "ALTER TABLE chatui_projects " + "ADD COLUMN workspace_type VARCHAR(32) NOT NULL DEFAULT 'session'" + ) + ) + if "workspace_path" not in columns: + await conn.execute( + text("ALTER TABLE chatui_projects ADD COLUMN workspace_path VARCHAR") + ) + # ==== # Platform Statistics # ==== @@ -1877,6 +1895,8 @@ async def create_chatui_project( title: str, emoji: str | None = "📁", description: str | None = None, + workspace_type: str = "session", + workspace_path: str | None = None, ) -> ChatUIProject: """Create a new ChatUI project.""" async with self.get_db() as session: @@ -1887,6 +1907,8 @@ async def create_chatui_project( title=title, emoji=emoji, description=description, + workspace_type=workspace_type, + workspace_path=workspace_path, ) session.add(project) await session.flush() @@ -1929,6 +1951,8 @@ async def update_chatui_project( title: str | None = None, emoji: str | None = None, description: str | None = None, + workspace_type: str | None = None, + workspace_path: str | None = None, ) -> None: """Update a ChatUI project.""" async with self.get_db() as session: @@ -1941,6 +1965,9 @@ async def update_chatui_project( values["emoji"] = emoji if description is not None: values["description"] = description + if workspace_type is not None: + values["workspace_type"] = workspace_type + values["workspace_path"] = workspace_path await session.execute( update(ChatUIProject) diff --git a/astrbot/core/tools/computer_tools/fs.py b/astrbot/core/tools/computer_tools/fs.py index c3236c8a18..cac1131f86 100644 --- a/astrbot/core/tools/computer_tools/fs.py +++ b/astrbot/core/tools/computer_tools/fs.py @@ -14,7 +14,7 @@ implement them and the main agent does not expose them in local mode. - Member + local: read/grep are restricted to `data/skills`, plugin-provided `data/plugins/*/skills`, - `data/workspaces/{normalized_umo}`, and `/tmp/.astrbot`; write/edit are + the current session or project workspace, and `/tmp/.astrbot`; write/edit are restricted to the same local roots except plugin-provided Skills, which are read-only. Upload/download are denied by `check_admin_permission` if invoked. - Admin + sandbox: read/write/edit/grep are not path-restricted by this @@ -28,8 +28,7 @@ admin behavior. Local path resolution rule: -- In local runtime, relative paths are resolved under - `data/workspaces/{normalized_umo}`. +- In local runtime, relative paths are resolved under the primary workspace. - In sandbox runtime, relative paths are passed through unchanged. """ @@ -60,6 +59,7 @@ check_admin_permission, is_local_runtime, normalize_umo_for_workspace, + workspace_root_for_context, ) _COMPUTER_RUNTIME_TOOL_CONFIG = { @@ -77,9 +77,13 @@ def _remote_basename(path: str) -> str: return path.replace("\\", "/").rstrip("/").split("/")[-1] -def _restricted_env_path_labels(umo: str, *, include_plugin_skills: bool) -> list[str]: +def _restricted_env_path_labels( + umo: str, + *, + include_plugin_skills: bool, + current_workspace_root: Path | None = None, +) -> list[str]: """Labels for the allowed directories in a local(not sandbox) and restricted(not admin) environment""" - normalized_umo = normalize_umo_for_workspace(umo) labels = [ "data/skills", ] @@ -87,7 +91,7 @@ def _restricted_env_path_labels(umo: str, *, include_plugin_skills: bool) -> lis labels.append("data/plugins/*/skills") labels.extend( [ - f"data/workspaces/{normalized_umo}", + str(current_workspace_root or _workspace_root(umo)), get_astrbot_system_tmp_path(), get_astrbot_temp_path(), ] @@ -117,22 +121,28 @@ def _plugin_skill_roots() -> tuple[Path, ...]: ) -def _read_allowed_roots(umo: str) -> tuple[Path, ...]: +def _read_allowed_roots( + umo: str, + current_workspace_root: Path | None = None, +) -> tuple[Path, ...]: """Non-admin users can only read files within these directories (and their subdirectories)""" return ( Path(get_astrbot_skills_path()).resolve(strict=False), *_plugin_skill_roots(), - _workspace_root(umo), + current_workspace_root or _workspace_root(umo), Path(get_astrbot_system_tmp_path()).resolve(strict=False), Path(get_astrbot_temp_path()).resolve(strict=False), ) -def _write_allowed_roots(umo: str) -> tuple[Path, ...]: +def _write_allowed_roots( + umo: str, + current_workspace_root: Path | None = None, +) -> tuple[Path, ...]: """Non-admin users cannot modify plugin-provided Skills.""" return ( Path(get_astrbot_skills_path()).resolve(strict=False), - _workspace_root(umo), + current_workspace_root or _workspace_root(umo), Path(get_astrbot_system_tmp_path()).resolve(strict=False), Path(get_astrbot_temp_path()).resolve(strict=False), ) @@ -149,7 +159,13 @@ def _is_restricted_env(context: ContextWrapper[AstrAgentContext]) -> bool: return require_admin and context.context.event.role != "admin" -def _resolve_tool_path(path: str, *, local_env: bool, umo: str) -> str: +def _resolve_tool_path( + path: str, + *, + local_env: bool, + umo: str, + current_workspace_root: Path | None = None, +) -> str: normalized_path = path.strip() if not normalized_path: return normalized_path @@ -157,16 +173,28 @@ def _resolve_tool_path(path: str, *, local_env: bool, umo: str) -> str: if candidate.is_absolute(): return str(candidate.resolve(strict=False)) if local_env: - return str((_workspace_root(umo) / candidate).resolve(strict=False)) + return str( + ((current_workspace_root or _workspace_root(umo)) / candidate).resolve( + strict=False + ) + ) return normalized_path -def _resolve_user_path(path: str, *, local_env: bool, umo: str) -> Path: +def _resolve_user_path( + path: str, + *, + local_env: bool, + umo: str, + current_workspace_root: Path | None = None, +) -> Path: candidate = Path(path).expanduser() if candidate.is_absolute(): return candidate.resolve(strict=False) if local_env: - return (_workspace_root(umo) / candidate).resolve(strict=False) + return ((current_workspace_root or _workspace_root(umo)) / candidate).resolve( + strict=False + ) return (Path.cwd() / candidate).resolve(strict=False) @@ -175,8 +203,14 @@ def _is_path_within_allowed_roots( *, umo: str, allowed_roots: tuple[Path, ...], + current_workspace_root: Path | None = None, ) -> bool: - resolved = _resolve_user_path(path, local_env=True, umo=umo) + resolved = _resolve_user_path( + path, + local_env=True, + umo=umo, + current_workspace_root=current_workspace_root, + ) return any( resolved == allowed_root or resolved.is_relative_to(allowed_root) for allowed_root in allowed_roots @@ -209,19 +243,34 @@ def _normalize_rw_path( local_env: bool, umo: str, write: bool = False, + current_workspace_root: Path | None = None, ) -> str: - normalized_path = _resolve_tool_path(path, local_env=local_env, umo=umo) + normalized_path = _resolve_tool_path( + path, + local_env=local_env, + umo=umo, + current_workspace_root=current_workspace_root, + ) if not normalized_path: raise ValueError("`path` must be a non-empty string.") if restricted: - allowed_roots = _write_allowed_roots(umo) if write else _read_allowed_roots(umo) + allowed_roots = ( + _write_allowed_roots(umo, current_workspace_root) + if write + else _read_allowed_roots(umo, current_workspace_root) + ) if restricted and not _is_path_within_allowed_roots( normalized_path, umo=umo, allowed_roots=allowed_roots, + current_workspace_root=current_workspace_root, ): allowed = ", ".join( - _restricted_env_path_labels(umo, include_plugin_skills=not write) + _restricted_env_path_labels( + umo, + include_plugin_skills=not write, + current_workspace_root=current_workspace_root, + ) ) access = "Write" if write else "Read" raise PermissionError( @@ -291,6 +340,9 @@ async def call( ) -> ToolExecResult: local_env = is_local_runtime(context) restricted = _is_restricted_env(context) + current_workspace_root = ( + await workspace_root_for_context(context) if local_env else None + ) try: normalized_path = ( _normalize_rw_path( @@ -298,6 +350,7 @@ async def call( restricted=restricted, local_env=local_env, umo=context.context.event.unified_msg_origin, + current_workspace_root=current_workspace_root, ) if local_env else path.strip() @@ -316,7 +369,10 @@ async def call( offset=offset, limit=limit, workspace_dir=( - str(_workspace_root(context.context.event.unified_msg_origin)) + str( + current_workspace_root + or _workspace_root(context.context.event.unified_msg_origin) + ) if local_env else None ), @@ -358,6 +414,9 @@ async def call( ) -> ToolExecResult: local_env = is_local_runtime(context) restricted = _is_restricted_env(context) + current_workspace_root = ( + await workspace_root_for_context(context) if local_env else None + ) try: normalized_path = ( _normalize_rw_path( @@ -366,6 +425,7 @@ async def call( local_env=local_env, umo=context.context.event.unified_msg_origin, write=True, + current_workspace_root=current_workspace_root, ) if local_env else path.strip() @@ -437,6 +497,9 @@ async def call( umo = str(context.context.event.unified_msg_origin) local_env = is_local_runtime(context) restricted = _is_restricted_env(context) + current_workspace_root = ( + await workspace_root_for_context(context) if local_env else None + ) try: normalized_path = ( _normalize_rw_path( @@ -445,6 +508,7 @@ async def call( local_env=local_env, umo=umo, write=True, + current_workspace_root=current_workspace_root, ) if local_env else path.strip() @@ -594,15 +658,28 @@ def _normalize_search_paths( restricted: bool, local_env: bool, umo: str, + current_workspace_root: Path | None = None, ) -> list[str]: normalized = ( - [_resolve_tool_path(path, local_env=local_env, umo=umo)] if path else [] + [ + _resolve_tool_path( + path, + local_env=local_env, + umo=umo, + current_workspace_root=current_workspace_root, + ) + ] + if path + else [] ) if not normalized: if restricted: - return [str(root) for root in _read_allowed_roots(umo)] + return [ + str(root) + for root in _read_allowed_roots(umo, current_workspace_root) + ] if local_env: - return [str(_workspace_root(umo))] + return [str(current_workspace_root or _workspace_root(umo))] return ["."] if restricted: @@ -612,12 +689,17 @@ def _normalize_search_paths( if not _is_path_within_allowed_roots( path, umo=umo, - allowed_roots=_read_allowed_roots(umo), + allowed_roots=_read_allowed_roots(umo, current_workspace_root), + current_workspace_root=current_workspace_root, ) ] if disallowed: allowed = ", ".join( - _restricted_env_path_labels(umo, include_plugin_skills=True) + _restricted_env_path_labels( + umo, + include_plugin_skills=True, + current_workspace_root=current_workspace_root, + ) ) blocked = ", ".join(disallowed) raise PermissionError( @@ -644,6 +726,9 @@ async def call( local_env = is_local_runtime(context) restricted = _is_restricted_env(context) + current_workspace_root = ( + await workspace_root_for_context(context) if local_env else None + ) try: search_paths = ( self._normalize_search_paths( @@ -651,6 +736,7 @@ async def call( restricted=restricted, local_env=local_env, umo=context.context.event.unified_msg_origin, + current_workspace_root=current_workspace_root, ) if local_env else ([path.strip()] if path and path.strip() else ["."]) diff --git a/astrbot/core/tools/computer_tools/python.py b/astrbot/core/tools/computer_tools/python.py index f9500ff7e8..b51c225d97 100644 --- a/astrbot/core/tools/computer_tools/python.py +++ b/astrbot/core/tools/computer_tools/python.py @@ -11,7 +11,10 @@ from astrbot.core.message.message_event_result import MessageChain from ..registry import builtin_tool -from .util import check_admin_permission, workspace_root +from .util import ( + check_admin_permission, + workspace_root_for_context, +) _OS_NAME = platform.system() _SANDBOX_PYTHON_TOOL_CONFIG = { @@ -137,9 +140,7 @@ async def call( else context.tool_call_timeout ) try: - current_workspace_root = workspace_root( - context.context.event.unified_msg_origin - ) + current_workspace_root = await workspace_root_for_context(context) current_workspace_root.mkdir(parents=True, exist_ok=True) result = await sb.python.exec( code, diff --git a/astrbot/core/tools/computer_tools/shell.py b/astrbot/core/tools/computer_tools/shell.py index 1e1acfbf9a..88d1d69e4c 100644 --- a/astrbot/core/tools/computer_tools/shell.py +++ b/astrbot/core/tools/computer_tools/shell.py @@ -14,7 +14,11 @@ from astrbot.core.utils.astrbot_path import get_astrbot_system_tmp_path from ..registry import builtin_tool -from .util import check_admin_permission, is_local_runtime, workspace_root +from .util import ( + check_admin_permission, + is_local_runtime, + workspace_root_for_context, +) _COMPUTER_RUNTIME_TOOL_CONFIG = { "provider_settings.computer_use_runtime": ("local", "sandbox"), @@ -99,9 +103,7 @@ async def call( try: cwd: str | None = None if is_local_runtime(context): - current_workspace_root = workspace_root( - context.context.event.unified_msg_origin - ) + current_workspace_root = await workspace_root_for_context(context) current_workspace_root.mkdir(parents=True, exist_ok=True) cwd = str(current_workspace_root) diff --git a/astrbot/core/tools/computer_tools/util.py b/astrbot/core/tools/computer_tools/util.py index a3930b4c6a..9bb71298bd 100644 --- a/astrbot/core/tools/computer_tools/util.py +++ b/astrbot/core/tools/computer_tools/util.py @@ -1,20 +1,48 @@ -import re from pathlib import Path from astrbot.core.agent.run_context import ContextWrapper from astrbot.core.astr_agent_context import AstrAgentContext +from astrbot.core.db import BaseDatabase from astrbot.core.utils.astrbot_path import get_astrbot_workspaces_path +from astrbot.core.workspace import ( + normalize_umo_for_workspace, + resolve_workspace_root_for_umo, +) -def normalize_umo_for_workspace(umo: str) -> str: - normalized = re.sub(r"[^A-Za-z0-9._-]+", "_", umo.strip()) - return normalized or "unknown" +def workspace_root(umo: str) -> Path: + """Return the legacy workspace root for compatibility. + Args: + umo: Unified message origin. -def workspace_root(umo: str) -> Path: - """Root directory for relative paths in local runtime""" - normalized_umo = normalize_umo_for_workspace(umo) - return (Path(get_astrbot_workspaces_path()) / normalized_umo).resolve(strict=False) + Returns: + Legacy per-session workspace root. + """ + return ( + Path(get_astrbot_workspaces_path()) / normalize_umo_for_workspace(umo) + ).resolve(strict=False) + + +async def workspace_root_for_context( + context: ContextWrapper[AstrAgentContext], +) -> Path: + """Resolve the workspace root for a tool call context. + + Args: + context: Tool call context. + + Returns: + Workspace root used as cwd. + """ + umo = context.context.event.unified_msg_origin + db = getattr(context.context.context, "_db", None) + if not isinstance(db, BaseDatabase): + return workspace_root(umo) + try: + return await resolve_workspace_root_for_umo(umo, db) + except Exception: + return workspace_root(umo) def is_local_runtime(context: ContextWrapper[AstrAgentContext]) -> bool: diff --git a/astrbot/core/tools/message_tools.py b/astrbot/core/tools/message_tools.py index 0f5ac7e5d3..7bb7eda26e 100644 --- a/astrbot/core/tools/message_tools.py +++ b/astrbot/core/tools/message_tools.py @@ -20,6 +20,7 @@ check_admin_permission, is_local_runtime, workspace_root, + workspace_root_for_context, ) from astrbot.core.tools.registry import builtin_tool from astrbot.core.utils.astrbot_path import ( @@ -28,10 +29,13 @@ ) -def _file_send_allowed_roots(umo: str | None) -> tuple[Path, ...]: +def _file_send_allowed_roots( + umo: str | None, + current_workspace_root: Path | None = None, +) -> tuple[Path, ...]: roots = [] if umo: - roots.append(workspace_root(umo)) + roots.append(current_workspace_root or workspace_root(umo)) roots.extend( [ Path(get_astrbot_temp_path()).resolve(strict=False), @@ -59,9 +63,10 @@ def _is_restricted_local_env(context: ContextWrapper[AstrAgentContext]) -> bool: def _can_send_local_file( context: ContextWrapper[AstrAgentContext], local_path: Path, + current_workspace_root: Path | None = None, ) -> bool: umo = context.context.event.unified_msg_origin - allowed_roots = _file_send_allowed_roots(umo) + allowed_roots = _file_send_allowed_roots(umo, current_workspace_root) if _is_path_within(local_path, allowed_roots): return True return is_local_runtime(context) and not _is_restricted_local_env(context) @@ -137,12 +142,18 @@ async def _resolve_path_from_sandbox( if not path: raise FileNotFoundError(f"{component_type} path is empty") + current_workspace_root = ( + await workspace_root_for_context(context) + if is_local_runtime(context) + else None + ) + # Relative host paths are resolved only inside the user's workspace. if not os.path.isabs(path): unified_msg_origin = context.context.event.unified_msg_origin if unified_msg_origin: + ws_path = current_workspace_root or workspace_root(unified_msg_origin) try: - ws_path = workspace_root(unified_msg_origin) ws_candidate = (ws_path / path).resolve(strict=False) if ws_candidate.is_file() and ws_candidate.is_relative_to(ws_path): return str(ws_candidate), False @@ -151,13 +162,16 @@ async def _resolve_path_from_sandbox( else: local_candidate = Path(path).expanduser().resolve(strict=False) if local_candidate.is_file(): - if _can_send_local_file(context, local_candidate): + if _can_send_local_file( + context, local_candidate, current_workspace_root + ): return str(local_candidate), False if is_local_runtime(context): allowed = ", ".join( str(root) for root in _file_send_allowed_roots( - context.context.event.unified_msg_origin + context.context.event.unified_msg_origin, + current_workspace_root, ) ) raise PermissionError( diff --git a/astrbot/core/workspace.py b/astrbot/core/workspace.py new file mode 100644 index 0000000000..645a0e3d66 --- /dev/null +++ b/astrbot/core/workspace.py @@ -0,0 +1,195 @@ +from __future__ import annotations + +import re +from pathlib import Path +from typing import Any + +from astrbot.core.db import BaseDatabase +from astrbot.core.platform.message_session import MessageSession +from astrbot.core.utils.astrbot_path import get_astrbot_workspaces_path + +WORKSPACE_TYPE_SESSION = "session" +WORKSPACE_TYPE_PROJECT = "project" +WORKSPACE_TYPE_CUSTOM = "custom" +WORKSPACE_TYPES = { + WORKSPACE_TYPE_SESSION, + WORKSPACE_TYPE_PROJECT, + WORKSPACE_TYPE_CUSTOM, +} + + +def normalize_umo_for_workspace(umo: str) -> str: + """Normalize a unified message origin into a filesystem-safe name. + + Args: + umo: Unified message origin. + + Returns: + Filesystem-safe workspace directory name. + """ + normalized = re.sub(r"[^A-Za-z0-9._-]+", "_", umo.strip()) + return normalized or "unknown" + + +def normalize_project_workspace_type(value: Any) -> str: + """Normalize stored or incoming project workspace type. + + Args: + value: Raw workspace type value. + + Returns: + A known workspace type. + """ + workspace_type = str(value or WORKSPACE_TYPE_SESSION).strip().lower() + return ( + workspace_type if workspace_type in WORKSPACE_TYPES else WORKSPACE_TYPE_SESSION + ) + + +def normalize_workspace_path(path: Any) -> str | None: + """Normalize a custom workspace path value for storage. + + Args: + path: Raw path value from API or database. + + Returns: + Normalized path string, or None when empty. + """ + if not isinstance(path, str): + return None + value = path.strip() + return value or None + + +def default_workspace_root(umo: str) -> Path: + """Return the legacy per-session workspace root. + + Args: + umo: Unified message origin. + + Returns: + The legacy workspace directory path. + """ + return ( + Path(get_astrbot_workspaces_path()) / normalize_umo_for_workspace(umo) + ).resolve(strict=False) + + +def project_workspace_root(project_id: str) -> Path: + """Return the default shared workspace root for a ChatUI project. + + Args: + project_id: ChatUI project ID. + + Returns: + The project workspace directory path. + """ + safe_project_id = re.sub(r"[^A-Za-z0-9._-]+", "_", project_id.strip()) + return (Path(get_astrbot_workspaces_path()) / f"project_{safe_project_id}").resolve( + strict=False + ) + + +def workspace_path_to_root(path: str) -> Path: + """Resolve a custom workspace path. + + Args: + path: Stored workspace path. Relative values are rooted under AstrBot + workspaces. Absolute values must also remain within AstrBot + workspaces. + + Returns: + Absolute resolved path. + + Raises: + ValueError: If the path escapes or targets the AstrBot workspaces root. + """ + workspaces_root = Path(get_astrbot_workspaces_path()).resolve(strict=False) + candidate = Path(path).expanduser() + if not candidate.is_absolute(): + candidate = workspaces_root / candidate + resolved = candidate.resolve(strict=False) + if resolved == workspaces_root or not resolved.is_relative_to(workspaces_root): + raise ValueError( + "Workspace path must stay within a subdirectory of AstrBot workspaces" + ) + return resolved + + +def resolve_project_workspace_root(project: Any, *, fallback_umo: str) -> Path: + """Resolve the workspace root from a project record. + + Args: + project: Project-like object with workspace fields. + fallback_umo: UMO used when the project keeps legacy session workspaces. + + Returns: + Workspace root used as cwd. + """ + fallback = default_workspace_root(fallback_umo) + workspace_type = normalize_project_workspace_type( + getattr(project, "workspace_type", WORKSPACE_TYPE_SESSION) + ) + if workspace_type == WORKSPACE_TYPE_SESSION: + return fallback + if workspace_type == WORKSPACE_TYPE_PROJECT: + return project_workspace_root(str(project.project_id)) + if workspace_type == WORKSPACE_TYPE_CUSTOM: + workspace_path = normalize_workspace_path( + getattr(project, "workspace_path", None) + ) + if workspace_path: + return workspace_path_to_root(workspace_path) + return fallback + + +def parse_webchat_umo(umo: str) -> tuple[str, str] | None: + """Extract creator and session ID from a webchat UMO. + + Args: + umo: Unified message origin. + + Returns: + Tuple of creator and ChatUI session ID, or None for non-webchat UMO. + """ + try: + message_session = MessageSession.from_str(umo) + except Exception: + return None + + if message_session.platform_name != "webchat": + return None + + parts = message_session.session_id.split("!", 2) + if len(parts) != 3 or parts[0] != "webchat": + return None + return parts[1], parts[2] + + +async def resolve_workspace_root_for_umo( + umo: str, + db: BaseDatabase | None = None, +) -> Path: + """Resolve the workspace root for a UMO. + + Args: + umo: Unified message origin. + db: Optional database instance. Falls back to the global database helper. + + Returns: + Workspace root used as cwd. + """ + parsed = parse_webchat_umo(umo) + if not parsed: + return default_workspace_root(umo) + + creator, session_id = parsed + if db is None: + from astrbot.core import db_helper + + db = db_helper + + project = await db.get_project_by_session(session_id=session_id, creator=creator) + if not project: + return default_workspace_root(umo) + return resolve_project_workspace_root(project, fallback_umo=umo) diff --git a/astrbot/dashboard/schemas.py b/astrbot/dashboard/schemas.py index f37449c532..bd72e1e4b3 100644 --- a/astrbot/dashboard/schemas.py +++ b/astrbot/dashboard/schemas.py @@ -95,6 +95,8 @@ class ChatProjectRequest(OpenModel): title: str | None = None emoji: str | None = None description: str | None = None + workspace_type: str | None = None + workspace_path: str | None = None class ChatProjectSessionRequest(OpenModel): diff --git a/astrbot/dashboard/services/chatui_project_service.py b/astrbot/dashboard/services/chatui_project_service.py index 4a928751ef..34e711d744 100644 --- a/astrbot/dashboard/services/chatui_project_service.py +++ b/astrbot/dashboard/services/chatui_project_service.py @@ -1,7 +1,17 @@ from __future__ import annotations +import os + from astrbot.core.db import BaseDatabase from astrbot.core.utils.datetime_utils import to_utc_isoformat +from astrbot.core.workspace import ( + WORKSPACE_TYPE_CUSTOM, + WORKSPACE_TYPE_SESSION, + normalize_project_workspace_type, + normalize_workspace_path, + resolve_project_workspace_root, + workspace_path_to_root, +) class ChatUIProjectServiceError(Exception): @@ -17,6 +27,7 @@ async def create_project(self, username: str, data: object) -> dict: title = payload.get("title") emoji = payload.get("emoji", "📁") description = payload.get("description") + workspace_type, workspace_path = self._normalize_workspace_config(payload) if not title: raise ChatUIProjectServiceError("Missing key: title") @@ -26,6 +37,8 @@ async def create_project(self, username: str, data: object) -> dict: title=title, emoji=emoji, description=description, + workspace_type=workspace_type, + workspace_path=workspace_path, ) return self._serialize_project(project) @@ -53,12 +66,22 @@ async def update_project(self, username: str, data: object) -> None: if not project_id: raise ChatUIProjectServiceError("Missing key: project_id") - await self._get_owned_project(username, project_id) + project = await self._get_owned_project(username, project_id) + workspace_type = None + workspace_path = None + if "workspace_type" in payload or "workspace_path" in payload: + workspace_type, workspace_path = self._normalize_workspace_config( + payload, + fallback_type=project.workspace_type, + fallback_path=project.workspace_path, + ) await self.db.update_chatui_project( project_id=project_id, title=payload.get("title"), emoji=payload.get("emoji"), description=payload.get("description"), + workspace_type=workspace_type, + workspace_path=workspace_path, ) async def delete_project(self, username: str, project_id: str | None) -> None: @@ -136,11 +159,32 @@ async def _get_owned_session(self, username: str, session_id: str): @staticmethod def _serialize_project(project) -> dict: + workspace_type = normalize_project_workspace_type( + getattr(project, "workspace_type", WORKSPACE_TYPE_SESSION) + ) + workspace_path = normalize_workspace_path( + getattr(project, "workspace_path", None) + ) + resolved_workspace_path = None + if workspace_type != WORKSPACE_TYPE_SESSION: + fallback_umo = f"webchat:FriendMessage:webchat!{project.creator}!default" + try: + resolved_workspace_path = str( + resolve_project_workspace_root( + project, + fallback_umo=fallback_umo, + ) + ) + except ValueError: + resolved_workspace_path = None return { "project_id": project.project_id, "title": project.title, "emoji": project.emoji, "description": project.description, + "workspace_type": workspace_type, + "workspace_path": workspace_path, + "resolved_workspace_path": resolved_workspace_path, "created_at": to_utc_isoformat(project.created_at), "updated_at": to_utc_isoformat(project.updated_at), } @@ -160,3 +204,49 @@ def _serialize_session(session) -> dict: @staticmethod def _as_payload(data: object) -> dict: return data if isinstance(data, dict) else {} + + @staticmethod + def _normalize_workspace_config( + payload: dict, + *, + fallback_type: str | None = None, + fallback_path: str | None = None, + ) -> tuple[str, str | None]: + """Normalize project workspace config from request payload. + + Args: + payload: Request payload. + fallback_type: Existing workspace type used when omitted. + fallback_path: Existing workspace path used when omitted. + + Returns: + Normalized workspace type and path. + + Raises: + ChatUIProjectServiceError: If a custom workspace has no usable path. + """ + workspace_type = normalize_project_workspace_type( + payload.get("workspace_type", fallback_type or WORKSPACE_TYPE_SESSION) + ) + raw_path = payload.get("workspace_path", fallback_path) + workspace_path = normalize_workspace_path(raw_path) + if workspace_type != WORKSPACE_TYPE_CUSTOM: + workspace_path = None + return workspace_type, workspace_path + + if not workspace_path: + raise ChatUIProjectServiceError("Custom workspace requires a path") + + try: + workspace_root = workspace_path_to_root(workspace_path) + except ValueError as exc: + raise ChatUIProjectServiceError(str(exc)) from exc + if not workspace_root.exists(): + raise ChatUIProjectServiceError("Custom workspace path does not exist") + if not workspace_root.is_dir(): + raise ChatUIProjectServiceError("Custom workspace path must be a directory") + if not os.access(workspace_root, os.R_OK | os.W_OK | os.X_OK): + raise ChatUIProjectServiceError( + "Custom workspace path requires read, write, and enter permissions" + ) + return workspace_type, workspace_path diff --git a/dashboard/src/api/generated/openapi-v1/types.gen.ts b/dashboard/src/api/generated/openapi-v1/types.gen.ts index 49e94b2bb1..4e3d94ffc8 100644 --- a/dashboard/src/api/generated/openapi-v1/types.gen.ts +++ b/dashboard/src/api/generated/openapi-v1/types.gen.ts @@ -83,8 +83,12 @@ export type ChatProjectRequest = { title?: string; emoji?: string; description?: string; + workspace_type?: 'session' | 'project' | 'custom'; + workspace_path?: string; }; +export type workspace_type = 'session' | 'project' | 'custom'; + export type ChatRequest = { /** * Caller-declared WebChat sender/session owner. This value is used as the message sender identity and may participate in sender-ID-based command permission checks. Treat chat-scoped API keys as trusted backend credentials and map or validate usernames before accepting end-user input. diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css index 9ada8cf9c9..9f3c1e4c78 100644 --- a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css +++ b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css @@ -1,4 +1,4 @@ -/* Auto-generated MDI subset – 279 icons */ +/* Auto-generated MDI subset – 280 icons */ /* Do not edit manually. Run: pnpm run subset-icons */ @font-face { @@ -496,6 +496,10 @@ content: "\F024B"; } +.mdi-folder-cog-outline::before { + content: "\F1080"; +} + .mdi-folder-move::before { content: "\F0252"; } diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff index 6401c409e3a7e525eee48ad31044090b82110891..7dc440f44856080599b25bc3de5ccd14df5e215d 100644 GIT binary patch delta 19424 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 delta 19255 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 diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 index d1a9fd21c8e832de71464e6eacdbe514926a5baa..2f42327fc62e2012a1087a2c195d5730455d7544 100644 GIT binary patch literal 16152 zcmV+zKj*-APew8T0RR9106!Q23jhEB0FTT706xY50RR9100000000000000000000 z0000SC0LI)rlUv@=DYmWl}Zx$Mf2sTaw zV`Ix1=>%m3WdC0h7y3)iB{vRZaup~DFNI*goM$Unqz&1A|1dy@ry$lD&$1sV5Du_6m>yBd+ z9Mz(@;HIs9rAzotXthJoomg9Ar?!HlW7^i3KeM-B5E=oyh>dsoe}@oCKNqE&%G&>? zKgC@@cgQWxT*`1Z@m<1J^ZM_{^4*1cAysq49Z^IaL~I)i{bvAxz`g!(K|PfX@%!B{ z2`-2Dq-iJ9!+g4$v5^OxPr)U0#3aZncKhl4l9EoEDG%9p4k2)ee+Ah;%d_vfY3NC%1}|JJh5Y)vEm{}tHO1HeA7%9SilkH(bye)0VxlNbS;nIcUPI8%?!SIh%3!dOLAEu$gsELX z7)7NBqCQ@|A5*_TpZ>q?q>EZ&&``XR3%S6l9qSlV%b5L{_giC@V`VI(D98bK(4-+i zn6mnGn{GY&4I^pZZP^0i0N4P&4}chCHx5WQX#;xBpZGU2*$M{7qPHJ3qx&>E`Kw67 zZ&W8&=>o`3G(Qq4ScKCRg+OG`$@LLMqctW7N+`jglqFaw72|GI5*(E72`)TnRYnMcgnxty4gLIvAm1&Sc zut%X#W@ywTTDx{J3=AYXb&}d?Cz;)Li?!c=afS_xH)>RZ4}3uGl1re^eQ}}JG{5(> z0Lggx(nSzbh!iQ4j7*j|ak3>ykRwTwTnY+#%9P1ht4;w*J(V6k)b{ID=%kahZn~++ zU3cl+cVDp=UMTUe|E|)<+ZVo+VPh*7C{P7HzDnW3Rf#l1H8HU|nX(XaQSJcLDPJTETY$`%K{T7v~anr)wjOY?t9-`Xv!4B{*Ve7 zRWFuzF$oZ0kzm0VxrXV68(40+h3!lCarQ_8T%|q%g(pM^J`oXtXwihKR1v9F zOKh&WBwDqST4D*ASG`KES1*Nr{geg`QhC?A)GoP1JpZI{EbKqeC%UQH{I0om9Mn@;0GN~J=5Jw3G~qb zzcNsv!cdzwqpgy_7!AxS6S?wDwP`ccqsQE8t1axd+tMYMto{;Mqk&&1ut9?sl&ysp z+F5I@yQH?I3n0yi{xO}YEbXCAJ*?#adFwJ8a_(U73> z0YP7h68$*klwWT->(8)Z|9dGDgmw{&M`RJvWKJAZ$&uBt*`Jzpmucn#i z8;|F^gAV%Pz6XBplZ4-B;`H)oHzfQ;lSU@|Lz9P={|-37-&u)(rbLQJl*kxh0Lwo6 z*!v+72Te^ct}eO=%$G&CsBR%6|=?~aYr4aJ1&t#G`%2^Bs8Pw zM3T|WtCKS^ z(nw$-duY3niCbGc`+CH|4qEJ5W8R$mHzC*#d@eXG@+`IqkYf8`$I;d?PfZ~L|2l+A z2&mYgSt<#^<7g~XHeo_QM(n>BU<&%{U0^p360EguqQzkhY@zIQAULlF#naTdQkxO@ z=#Cwz76A`?^%et7lu^5sia>w5A{dVK= z9(ZJ~Y$f3%@Mu7gSbOWLR8BXh3FdMy-d43pySGq1l=HgBWk>V!)$0wp<>mx^sZ~GO zMMvo8-$@LF)BzgQqEO*3lD)u;!6$+V6XUp(emij=Y{{fuTe!MM8W==SoE;m&FkPiQ)+#C0x*bT<9%ma*^02;JwinA#vf8scF5e*hd49T=Vcu1o znT3bLv#(4~Nt>?=b9w4Nn%nj#Ylk|~5U?BD@s!)KZW*0i$H}xZJ#cJt))|_c_Q)Ea zuqWI9Hqpob`svOBGmbMt2I`Txd^2si8}RU_|I^={*%wxs8MTPUi9W�k0)(T`h{o~v&XzGpXe$KN z!&btzaP#OLDXTsVY(j?i=~nK`7y|_Fk zc~%$IT|W|HV$BOFKs|Nj8V?h99kEJg3J+C}lI87q((O1_`L}YJdUx>n!ChhK9?O-` zn!2nLI%4eNMav^fD$bR%rbWxyKL@m09%fm3JQ{vd2!x-ykQy?hP zka3q@Ldv$I&cnDrk@*1u4~*&Mtvj)%3Ka0eiV3)R*C3kBi5-uC zXUG;h2*Yfng;IL=k4*fG#@;({|t*m9wXgc&CL_O5rS z{bcsn)c8NW0eWo`i(4mRm-S?!Q_1UOFZ)AOB_29MQ_Zu#U;Ld{6{s6<(=9Xh+36Vk z2P5fOffZIr;4nZU_;1jGrJU%bQWzAss-)PbFK6tVC}GIJIYbH36vCPxzTzqnp#zt2 zI(TC{<=l7Vj-~r;6u^m8%nC}1fS_YA6&Pm|OG~50rE+1hIJ01|*|nvjFGI2|jetGG zs|ln$T7nWoa=Z_R>NI`0^m5C04&6~OFji??)mJe^1au!K9|@puzk8&RfPjIKF+~V~ z5jGG?rdK%@T1JI;sUzuy!gTC|Y;&5^os5bc2ciP?0*FhxFF^-*TgkU@-xHgH+cTcr zknibzn1&C(Y21rmrsHJ7T+rD>!W4SIQ@bZUfa%F^?;;Z}t9lGahu2`YAdL5k1i}U8K~qGX$8MC>js*$HjIdh<1$Gk0GRUP0tBn`s^X_@}ZEOy^L-Xz>fik zZg4W48VBwDa>1MxF0XlUdX)p`dS(B9i$U5 z+Y_Xfx3||KUQFxlU;o#De)8q>+t;qDY9l3w*2q?g?&pCc_G++b&tBXa2piR;&{%Zn z7(BFe-{MaAT!!*V3v-17eq!0x0W1plRvzsg_m#o`uC$!?k038%Ef~Ce6Y0Z~_UYEd z-ZnovJJzR4KcU%7^a6bEgLI#G_kDs{qL+|$YIXmxOPnYO_zX|7K1PA(UWM=g;%6Pi z6CG!&t82lEs_50j{Jp#QEZ*nOjZtOq;}}y4@vf~;%}4KE1qiQvc!F$Jdv|dskillk z#eSR1fTiSsaUO<@1ulbpOIrpmIfb;uF zr9*7{n81j6`ZaQXMx1o0!2%iY-g<*dKcu*g<8R$4QhU}H@`LzrDUdEhYR5Ylc+u?Y4PZ$s+xi4Bj0^K^ z7`PXhZf<$033pd7J3VrCfaSo0XfHYMm6%hG*bxbKzlVb0vd0e)kid}5;h8k%c7`RG zA)zEZT3#oz?BoVHtx&H<&9XS?#U!h$SVpnHB)Ck5*Gc35PNMRXQ zs!Wc09ExHeeH)*N%X@k+T?(qVVSZcMfbK&MAcFV2WndI<<}=+9Anu|D@;L{_B_w>HPstDK+<05AT)P8WQQ&zUolSdePC z990v&Z<>7!)mqDf&iVzwI+XfL{rXG)+FvB#{1B{R}&AfOrpR3TjYk7!rRRRSwDccZ8EgON~1y6_Tyc$+Rkxqh2Y(X+9MWM zZ=0FqtNYMiiuN3C4VkH(t4{T5C7mls)_&`0`{aXOcfE;GEbDoVAz+!Rh5LXEsb$+c zUjI$HV5cIj7tK-6k(YvoghSQoAhgB%`60P}=ANA{kCkBSxo`T1(fNWg`@=d3zk*-F z=1QW(X6|ae`6i8U?mk~N)nda+@nJL>7uTn8B_17E((h;5oORY)6YvY2a!1|Oc(~tr ziJiHd=}e<=s!eXAI+ERW1!iKp-!Pi!NV)I z(oD2^0AyIM=irewd1rVl3JCcrBS^RVq3(<7ib4vwbavfB|CQ#|2QLO||E3!oxun|^ zs@ERAOb2P`_XM3@t=nu%Xd7=mT)n#TAln|Ka$ZYpjsG2G+k(PS*S`|p?Im1+c3A!2 z^|#NjCuUsZxRlMG0Sb)O>lc6d^`#TP z|0W`;?xy>`B)APma1b#CxoO<13IIcDi4ur~qO62kvYk$uxTOv=B*uq?*Qph>;zT_8 zlPioFjUk)87gfOlOzgkO5+Rhfkc*-bIHKm6gii>TtOMc^{7&c|gknU2Q2VG6 zN-nHiO)i*^kBxRW@i_*PUGHYwgOpKg<$_S=2-){;&9ojSRliMjDzCrNoxG!dGWKAr z_h;`Nt}$41b%-Plq`nnmx5S`>jXXqk=Y?e>Lp1#sjdHC7Z3v~)O*KhuACBAEUtC{e zejt}p>7wt;JP5>JJGrm^M#!dL)xuWh_rMdW=L-2`qTqP~ab4-Rrt-PzqWz^k=`U7;2n$gFDJ&v#zig?3twfzI4(h^KoA`Bw1Hu zP6UBC6UYb77tZy>x$?AQo}h{siZdDJpq>>HGcz1XiYpzKPs>4knmfWH898n7u$UBc zk~wr!MJyXJ1T>o+)lN_sjlhzM{vQO`%>fIIAVaA(l|(}8b41=3t^w{w(SuhfKY$+= zi^s3Wu}%KaHs;79g7+SwWU5lWZvVH&+J36OFM?N=pPy78yEYLF^gM9|s=rx zSCph?=5dkMzH95zlWp2(4%Slj^yV5yVUap6Fx(EG)SS>}&NvwhcnG>Gr{PGtm@W;r@*0Rb8pAxPocc z#1?*SsL^SOwO*#Pk#5KVO$i#a{I0K?cKl|y6 zgN?e+=kCw>i=O8S&B^SGIRAJy{Cw8UJTJPtuf7ae8B&@`R&y?AD{d=N)`E-vMo@pH z;EiCaP9{vC^O<03{Uci4~yhzXwX^s48>7q}S0Rk}^!t!CQ1jz`zg zBEQ+4FTnue02%6Clfbk@`iz_34TT2w*!@5y*{hF)L)}x@l112|JNNpR)0Ep)t#|2S zf>ziLmw>14c7m5L9$X(K6S!rGVhOEJr}l^ac-OlI8snC}1R*a94rie5{C92e;=#(* z)rU@w8cYqvuShZ%6yW2mqNX7gm9m<{ zc3?jM*e<}VVN3?LonalP1*SqBt~^Lri87l#M=nth2lBf?&iy=dK30KSC}gmua-lAG zNzt>TMODhPqD5ls7Q*6(Y^Xp+c8qufuH4IK(4KjU@d#4xSBcQWW>P}vE*P77jv^A5 zISEd<_0XvaJm(J~l_uA7cl`rdI>K9N84K|sF%X;&^O|DL&md~mAcYdTToKyk@qi8N z@45+L_4cL&w;1YVN3$<2cCOOcF;QljTKFaq(!hZr@7y9xrC*O{{Ki5=rI%8qVl#LX z5>#bP1VZ`%?=SFVOs%%*e?Xc?PurH@u&6SW<(p8~VHogs4alJm7C5BZs_&G849TPr z-({fB6p?%Hg7g9rcz79AqFn-sHdbZ?kO^cA_L3Z4iz?o*t|_O+N;8ZYGj0GkO+9`^ ze(!@*^}hQ)aGzf#!I5X1S8u+4`8^&i&%11=afwxzAR$?STP!TkiVqk^H=THNyOF!^ zWQ=>W3Z%p#zeG-$1qi7yEnHJ%@;!q$o06(jG}6M2;C5Xv2)8mAzz!kEwuC4mq#%FY z?IOvMnIHyMQ zArCJ+G0EGqm(ziqu^kB{UA80x%!hq6^`#WBY%P|#8m=sayGew&cG;fzK?yyRVEeGTxs%u^?}{MEuF!gW`J-@qGl77&51nxuBkdTG zE(+G%cy>|jTL-1-uhQj^b=fn81-1f_w=HQ4q*^F05snJB=n%%->E*@l2tqG@~;txs&VsQ}XKzS~id4Z4X|g zp&5X;54Yckxc;M+egWa(Xht4SPo4bDw(LuO`FQ>YG*+i$6{Kn(Ndq1;yFCM|8s=30QHoLdjG`DD!t?|@}hTry>LK8B$117g1BH8dGoSkA* z#}p2_Xs#qoO(vx%wy9_su<{{q#j{-41DT%M=YSp+%zjTwscb&)$p*xJf_cBIbEI**-$VX_KRj9O1VkjRR51m8p59zg6+=PB!`m!CQ^z#;Yd zLmM6VX^GsL=>IKdq9)`^@~0=LbJsn=*KJ{8NcQ!?iwLA=s#^GOitk+!&Clpg;=li2 zjLt)zIQkuUcVm4zOScU+7+!;H&iDqvl2k>^{F7(rPaQh)^=ZDhWBRPJjI`2{h7qY> zaH*SQk5S4g=IH2iCUGWL3S41`LSe2-0g$2b8d(*$qu|d^l0W&gpo3ebk;Ns8w92B) zBBk~$tc)Ist|X!>N4}I;t`FvB>-E_{Gc-X5M9^bhlfBck9HgPE9FLcs9jQQg2uvGQ z9pe>lw^NXkn-N4HS_txT-Ghd6r{TZ_2y*jw0h9qaoUL`nOd&;mMn=5?w+ESVdAvpw zU+X|5wLywJWtBTioyaX@fF=Cxm_@d#0mOj8Ekd2}_?GMh=O+m_R&VOu{xSxJInC<)~U4d@1f@==l~{Kp%a1XvRUE)^b^ia&UEI4!v{DXB6UH!bRr1VI1>I0;o& z#FV=;X2GCm@DXp)q`iRqw>bIOJOem_F>Hf4K3@m$;E~QwH${E(4d{G)#R|9kb9mnJ z^*0R-PrFxmJU#9$Tb=^pnxp#4xH$--JSVtzN#@Kq!vwEr$Al0<5YS+Bmz5nq{=k&f z4-O4JSPna~B2Ao6uZ6o)D*DgYG`O~T1GhiD%*$)R&v4GD%}%buWA z@|<+4wTwa{x_kC{0!*=DcU?w7*474$s zbWP@p==?D7Lc=}+b^#OybC@|n4-h+@gJT|609Hc;<#v)b2HD#SB4iM_13GXsd^`t8 z!VwM>2zWmQh={bAtIkuVZg9~Ymok3Z%J;zOeZMXI2fGDdl znRv>^z8nrt4-z0Tof(uKY)}PDf|ax~gefs!Y}~DXDtucsALkoY0=@t|Zc&CE7)mLO z8M1`WcEx>knwnUvy&{d*E+#Or0L8H1+QsQknYdrxKSO|6UM6^qw2AFTM%)*#xaakc zk2KsH44A-3raNM-*Qz5r&S!qe?=y~WISGQLD+wfrbEj+o3{H_0ZZPnBZA_ATSYw80 zNkN-M;m8O?=-?+nLmD_kIVn_PPy;*i!;wZaHe6!OSkVBq2H^<*X9bK1B3cA2H5`Ho zF_A5A_^=T`wrc1F#4COtdRE2ZVG_oosXT9tQ7fngSzS^IDdYdkE~dI z27-y(m4)eDzCO5vA$Z{WbjR=5&~WYJteE=jK+ZB>{}aE9HfL|%Bi)+{vik&P*T-c4 zxZvb{QLay0$M4lkbWjd8&~ZvzV6&yQ#fu-#6Gw(|xnYswc@M?$ZD|eAtOKS!l1|K8 zEP#Ph%i-~k`s;>4DM!bVMylR}5b{(CO@*LoiZ&8F^cmA|#0R&x; zhCI6zBr*MZBs^K zW;BEcit>FfPFi4i!<_X19p}k<5Jw=H#)_+$Ax+Xevk6&|(d77n5}h0;#|A6~>m)~G ze65)2HJ!07u?vbriPI{F$}|RK9u*e2{KL2tx~?@P2VPzp>|0xck1x61zOg-Ei}_u~Ddxxm1fGoNg0I;l z*f@J!JT7>d_^iOO8weMImUMB|@8aJ900~J(jvjDHXc)oiqEev*ZQ{`36AY_!VDBOD~d~*HrvN;vIF0T+{t0 zl?rcjwA#KtKzNMl^1#e9rzLC&A@!G@Ir9}{*+>ww*PKUv%`1v-P7^FB0_Z6AD4P^+ z?xc_UGt+Dj9-vr}ohxm*hmZJ9K4c&f0&);}H}T4cas+4)oY1Y%nwK5ji~#ms;O^Lc zb~q0%Cypl%oha+Z&i;Ieg&?bS;nwSnwmRH>gE>`nT&!(CK60_gJnq+;%^xu-6!G-N zZQe}i%ZsN>NN)7V%+&_X%0MjeLy0dJmQGuiri~JGvj`TY%-w=0ZQ8PFd-sa#EBP?} zNPN%$u(Y&!Tp7#diCAylB$$@cE^!2<$=vcj18Mm4@Wd3GbMp752ga2?1lQ zhM<9KPC~xZFl~iaU>pFInb64Wp%D#a>4qwab$QFdpu8Ox1du_o4IufSNXfZ=x&mI) z#tX4@Lry!8f~r*BY+ZgIjG+0k^Levtk^u?;kD*E0fp|vBMG&8M6&wm0g{bBN(&Iv@ zcq!Q=<}iv=unuSTK|NQG;W`EPJgf`MSG_DDji1q(IDSob*$DQzO5k z5#$oO?R{b~(8OcOCCN8q<~_t8&J#qt%gWrJg#CJFgG>F=5D*1SCjz+m7vSGh-*|+%!{t+1$DHWoaY1G|)ub zXvS=F+V0%3(KaM(0E!dcd6g~h<=lyfG|IS&CJ<`{oQu4=+P3Oy^lX|m8Y!@W;vJ0` zzG4M6(x~xTx^$;iekHr)iu~N;d)|hQ;KJgw7cGk;<9NTI5_6MV zV^4eqA{il9=u#L>9q; z^Swwif6FT5<{F>#%-S2_;1K@zdXpXm5|uyjfH)>Re{Jsa^5-CFy(H7zfGkLz2CvEJ zqqHCtG2X%XmI_8Xh{lwY<$dzxE7+0*r5q_V{%;GUv;QO0vZ7<#1spN6AcDF;}h zr`Yor_*HR5S|uUkb;O;I z>)?q9rB=k7ANR_i%DC!(YJHn|!XhOp6s%L!#GboAoIvx3Yz-1vyC&e#{vG=Q9zM)? zvsv@-R#$b%jkk9EvFU@RKX!x)xy6Tzkb`dtl|9GHT~b5 zKi2}!dp!)#Z1v$R1P9u5gC1KGW@TQ;JXm{=71w`eQ_{fei$dlO$yn)w8_{yy$X(li z{qO(!?|ytMwdy4#L-PiY*<7zjSWK&F&p=+zp`?3lxchlYajoTcdM(hddk)&9<* zU7j(1a12T2HK5Fm`a)d1YS|laR8+c6i>_P&LZK}ibfH%yNYEz4Yvg?PI2)$`UqGA6 zf)FR~fxan0AnLsk^iHaHEO?&y%m$w`>&n1?+-sXC=apdxxn;l|RF%uQ9*C9PYXhlkYD|A~QCZ93^R=3lp?GNF6#gZA zfzfQ#&||O!EgSP-LDly?3$OeOjHYevr_HH$z#6Bzpiy;plQW4~l=60g9*BkblK>%x zSp#mv1I*^f-efRXM3#jMX9H*GvwESP)d(V&(*+@LiGR<`;F zvPalBr8j;+_R`2K0c5}DAIM-wJO8>K3fWDakK#fEFmqe6*_Lqc2_OKUjr2Cuf&H-A zK{`NevKf~dl1r*OJMEL5ixUcK%LpU-W4B81Eb)1wC*vjPT**1@H76h8{wB#>iJG^^ zgFp8<&kFKy$B~wW*RJ$5S*1MH2j|=)BTpyBp9U~p1TZXaVD|hbLKG7HwufnW!3zJ_ z|5IH#82zRoeuNv8-Ckc3$YTdNPd2I0-dJep2J_YcFag0S%4m8TmfF zJ`srSu_QTXE}QQG$K_e9$t06MUb*J(_M@@?I4)lvd!am9-!1O0KdOA;`1rEppV*`A z-C)HS!2|Rlk$w<1#{Q*Ie59zU9CT9N8Q%H6zSQOSD#dEA+cM3MA;CXtVsA}Ti>p2x zuJ`fH_s*DBf9d_I8s&#d&0oP8gCUYgX=FxZIuTZdVPbCD${%yQS#dtBoWEA4amAQK z#ST!#a3f#&uWXi29LqcV$2ZR9RI!02lb%@h{MxH{zKVlIhcrPNF#7nBBT6MJpzEez z+B!;&ZUqdc6oL%@SJqt4E!&qJ+oRaUcH& zJ*B)w1(ui*@afrS6kat zA*n0LbRI$v%OChx>fFOipbze2L=~6a>sr$1IN=}Ro3+i&Wh5lafVQ-PJ32;^KdNZs z6K1p?YMUWXm{y!YX&)Raii%e>WTa2Pfmj82gJ=XUCk{g92MU}WKeAEd4PcLBBbYAThkXS|bp3P@*sF#y&dKzvoLDky1guBowYqb1 zPvSyO61t$1Kz#_1)ul2cx0*~-032e_&>>?q%cvu(puN{V2OiPbdk@U($pGGhU5ZAQ zLJcheAp;?*v=8)ImT;IHcZ?(=37&~G-BIO-H9!hc_7wHVDctN+5>-{bU0t0ZG)3lI zP@^2}eZx7aoN(XVtyP`3KYM?kdKSNsW5^Rz%qN1yCez2oC0~ibW3D78zk%V{8_B>E z--HCJ+p>HCGVvLei|>Mu9moOcKR?(qW#EJ*u%otN_0~YYdg{GYA z?FvElGI@+XXTwV`S@HQ5t-d!US-o#>Jg)cejAUK#^E&pwjNk{lMP0*%%hO~TwQU+D zU+VesM(drne#*D zkPCP~Byx<3^q8QkPckG9mIOOqTo5vrz)TF8pO>;;oI^k-9NB?Tu0AQdVy**m6S?s@ zHLsO+{pxkj^K@Ux=_fHMF_TRWoV1%DaC}yvk&bY?BWMEB=BA6V!xSKzJ%X)ff2z_5 zy>=2Zc_OWd-x0eJo+6wQ+rclpfQ8!@{)aTA881M92G>h649SY0^W$LqAxk?5-iB27^Z0(~He)`$W%cIYo-fDGY6IMC? z6x=Vp{pqK`G~n7!E3_gS(FAFuZ9{UWQ|`m)3_cGh$s!zLv_)w5aTYE9)F2h|jk@c!t?cS#nJWE`4 z*88=B1~6F+LtH^#<|>Jd8gp>D!D=?0O53p2D5;s6THFHgp;jsoMy)ELKH1Q#wjM)r z21UTTRJ%VDc|T?gJ`)S7lB)z_wufbbUk9uR^jKNo^Eg}_oGeTZ2HMzW&2q1O)pG81 z>ufyYw%7N*qu(LF-~0{+71EOO>_AowQ=1=~n~+zpjM7{@0%oEJn&5^dqwdkA8;}Xn zSFnSv(O=xGzt6AxvTz((Rzo&76=TaNIJJa4KvFKPOZT6Xqolu>dFH4cB5E1N)yo+4& zfJ;C)Ouh}Wt-6{xS5q_jq_X+|Wtl4{`kN@f-}LNyr9?O&jq@Q{UOcbsJpKcLnb-w; z&HGH_dSAx`&jd0VM=DR!R>lvQgEAu0BP0>hav~^Qn029~TP*VtpZ+o;SQ>#3jmQAN zPW#0%4^5d&R;t&Dps6D76&AGVl`QX>J5VF*GIhmj?eiSd-7JBdXcHvB0-w;G5cx>0 zb{DFbh!OxS#LQt!@{IHNjZ>v-gy65WE$Nmlfxigi!$qgf$c0M!exJ-e?(btXg!8vZ zj-lldt~>P?^+(_+MHL!|01-eWA-bEGDU05WNOIBuQ;~xoKcYN+WJCmjF7IPD-p)-g z9HA&+cIOXa(<2fyt$;t;MhAoR`7K8sg{ii??LiSgbr;}cAt^H32Xt8|@8Ly(xa zqyDo43Mv39K$n`D(jzuagNHrcBu4)C@4i5OdPhTDYX+s<~38 zk_=KpNxF}13}s0~bg?~=ztb#p9)XR0I7EfDUC>4ymr$OIY%PEToLA(K&2cT+KQ6719@1#oJ=rk~fpE&Q@6^66F;R6(oV4h%WF zB)_U8!v2n=~LaTK@Bc7(LTEG^_#o824pOVi7B!>#MDxPWj?}xt1YH2yUg;Q1z z>N0o*P_cx_=J39odui!5U8Em&L9^jIABwHwgGi=cXrmRu{HzC8@4N5cf8f6QU}A+z zy|Scm(mxPOz8^93e2{hK-s@Z8ELKSJ6|(Aj zI!knN`bBE4D3D2C$&R=b)0N2l5g}KrepjjEK-^7h5;!q*Le+xg? zoFoW0L68%&H%9IQI+wquaQdvm>1#4l{X)1)zwfupwdLf%Wno@%fA$6b=$qO&D{v^i zkRPw%7~i~~9{giK-(Tf_^#!P{1or)TDw^~pK2Llm2yW_;ZFxiV#>YRG#X^CY{5(kQ z^+`?h5#oJ+`^4uB++saFj%hyDzno0d8fm} zwz+4m4K;0K%emk5THA{gyLU2bbtzlebI)s^R?sQHS0s3PNe+C+4x1BYc<>qz0}z0S zDf-55+?uw|tNN1s0LJ3wg=g75&eQoN$tYmXc`t8(eOLzr^1LKoXj(?u`tEJ47!8)u z!SCSsXku8~0?JsvXoWpb|LHcrX&R0~$DRa9BbE|9AU8MpL3{Z1wUb7_xA_#UHeQy! zBYJ6kyf*SCf>Ganf!}h8ML{HZd;QM|h=lteenq48VhNrLSfOT`Gw@Bo_A#0b0ihO% zmlgiHXc2QfR)988r>~1*QrZE0O*772qr7mYc;qi93l)xW#tK2J@$5oJ9^adfDx586 zTUr2V7w9Oa1ofTIq+oT}*gMN!^C3jm+XO2uhEn-H-9EWaKBhr!f^VUX32&2Yt|RlG zEb)eN^e^h#k}Ei5_}rxO6?HFiSYv)rv1-;VN%)-bxkcJJm!AWP+59r;rL?fyljs@M zGOu}L;(sq>87~uG7D+RnCq0$0Nw!3zktk!_t=C33Z<4a95`FrCtbm2whK23tt?YhK zR+hx*uwqPmb`Bl0?tlaiNW=-<32*ZpU01*wb#!%14K}w_mN{?VDvu7ReD_oZJiKLY zq*|DIEhhE);OXsCZMV8@V59ul#ex_cQ=mz%KJyq;eJ=ul8DeU+F^Ge@b;v^37~KwC z^UL|ce@AV06=@%$$z{4twK_^2f3qR6=up$wiPo*~-F>YzwpeP)luZ`+z^*e=zFA13 zLpWUE$l>AF>8VbrPFm~})TW;E(3h{Z;%K$?MnpcR_Qj4B)rSV)q+4(AKZZ?TUX^WY zaVRATDDqPb4U@D(hW8@VcgF0^$A&O6zp1}u9afM+Qzi$N4mYb%Mh|h6FjK~97`Uph z+;%z8r_lAEpKXm@Y}yKWCstAWfYLadQsHo!0kzSmdUYL3K<95=mBR|N?m3OZTs)}} z+Gkl^2u<;AQNtjK5n-ByAQT0yMkCs6>h_!hOzl>bYL$VO(;N0Ji^Eq&BZa@!Lu$e|!* z&0tX((A3xd%Fx$;JJzRgJ(r8~F)@y_vxYfRFXW$G#mhyb5*M_roGGs54!(tn3m z*2QY5x>`K@ht3(IQl{@w*TJUUL*;&y&;(tkQs!vR)Q%aq?@p+%0a2SAZ65#jjg0$p zecFZcZLR{>@I~md2{ZUqhxs8VojmqMPu+1-4n4atIUU&48-fteT`0L`Hs=YY6D}7c zM1cJMLwI5j9z0R=zbOq(#b0!8$W?ZOtO^CW3%?QOSLJUH#2qgG>4qjMAQ`fnTKbHJ z2@m+cxeUNU><~f~eQxIraoYKo&9}vzLmFjuxB40SGnr*fX9=^fkLJ-$s-@jCSwL^5 qvQSq`H=@(ptn4nJkvbX}V!1z)<;-JpN2-@%LG2gRyB5vb4gdhQf*BeB literal 16008 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