diff --git a/README.md b/README.md index 26cf778..7d7bb6a 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ python3 -m server.app --path <当前目录> --port 8091 --thinking-mode - `list_files`: 列出目录内容 - `search_files`: 搜索文件 -> 说明:目录创建/重命名/删除等通用文件管理建议统一使用 `run_command` 或 `run_python`。 +> 说明:目录创建/重命名/删除等通用文件管理建议统一使用 `run_command`;需要 Python 时先探测并选择合适解释器。 #### 终端操作 @@ -119,8 +119,7 @@ python3 -m server.app --path <当前目录> --port 8091 --thinking-mode - `sleep`: 等待命令执行完成 **快速命令执行**: -- `run_command`: 执行 Shell 命令 -- `run_python`: 执行 Python 代码 +- `run_command`: 执行 Shell 命令;需要 Python 时通过命令探测并调用合适解释器 #### 网络检索 - `web_search`: 网络搜索 diff --git a/agentskills/terminal-guide/SKILL.md b/agentskills/terminal-guide/SKILL.md index cce7fe7..e8c62e1 100644 --- a/agentskills/terminal-guide/SKILL.md +++ b/agentskills/terminal-guide/SKILL.md @@ -354,8 +354,8 @@ terminal_input(command="vim file.txt", output_wait=5) # vim 需要完整 TTY ✅ **正确:** ```python -# 使用 run_python 执行 Python 代码 -run_python(code="print('hello')", timeout=5) +# 使用 run_command 探测并调用合适的 Python 解释器 +run_command(command="python3 -c 'print(\"hello\")'", timeout=5) # 使用 edit_file 修改文件 edit_file(file_path="file.txt", old_string="...", new_string="...") diff --git a/api_doc/tools.md b/api_doc/tools.md index 4234e3d..922835a 100644 --- a/api_doc/tools.md +++ b/api_doc/tools.md @@ -26,7 +26,7 @@ "label": "终端指令", "default_enabled": true, "silent_when_disabled": false, - "tools": ["run_command", "run_python"] + "tools": ["run_command"] } ] } @@ -38,4 +38,3 @@ - `categories[].label`:分类展示名 - `categories[].tools`:该分类下具体工具名称列表(用于前端渲染/筛选/白名单提示) - `default_enabled/silent_when_disabled`:用于 UI/策略展示(并不等于“你一定能调用到该工具”,真实可用性仍取决于服务端策略与运行时环境) - diff --git a/cli/src/eventMapper.ts b/cli/src/eventMapper.ts index 0b172c9..115e559 100644 --- a/cli/src/eventMapper.ts +++ b/cli/src/eventMapper.ts @@ -158,7 +158,6 @@ export function reduceTaskEvent(items: TimelineItem[], state: EventRenderState, function toolTitle(name: string, args: any, status: string): string { if (name === 'run_command') return `运行 ${args.command || ''}`.trim(); - if (name === 'run_python') return '运行 Python'; if (name === 'write_file') return `Write ${args.file_path || args.path || ''} ${countWrite(args.content || '')}`.trim(); if (name === 'edit_file') return `Edited ${args.file_path || args.path || ''} ${countEdit(args)}`.trim(); if (name === 'read_file' || name === 'read_skill') return `阅读 ${args.path || args.file_path || ''}`.trim(); @@ -193,7 +192,7 @@ function toolInitialBody(name: string, args: any): string { function toolResultBody(name: string, args: any, result: any): string { if (typeof result === 'string') return summarizeText(result); if (!result) return ''; - if (name === 'run_command' || name === 'terminal_input' || name === 'run_python') { + if (name === 'run_command' || name === 'terminal_input') { const exitCode = result.exit_code ?? result.returncode; const text = String(result?.output || result?.stdout || result?.stderr || '').trim(); if (exitCode !== undefined && exitCode !== 0) return text ? `exit code ${exitCode}` : `exit code ${exitCode}\n(no output)`; diff --git a/core/main_terminal.py b/core/main_terminal.py index b56eadc..19e72cc 100644 --- a/core/main_terminal.py +++ b/core/main_terminal.py @@ -131,7 +131,7 @@ class MainTerminal(MainTerminalCommandMixin, MainTerminalContextMixin, MainTermi broadcast_callback=None, # CLI模式不需要广播 container_session=container_session, ) - # 让 run_command/run_python 复用终端容器,保持环境一致 + # 让 run_command 复用终端容器,保持环境一致 self.terminal_ops.attach_terminal_manager(self.terminal_manager) self._apply_container_session(container_session) diff --git a/core/main_terminal_parts/commands.py b/core/main_terminal_parts/commands.py index f9313d7..f00fcf2 100644 --- a/core/main_terminal_parts/commands.py +++ b/core/main_terminal_parts/commands.py @@ -368,8 +368,6 @@ class MainTerminalCommandMixin: print(f"{OUTPUT_FORMATS['terminal']} 执行终端命令") elif tool_name == "web_search": print(f"{OUTPUT_FORMATS['search']} 网络搜索") - elif tool_name == "run_python": - print(f"{OUTPUT_FORMATS['code']} 执行Python代码") elif tool_name == "run_command": print(f"{OUTPUT_FORMATS['terminal']} 执行系统命令") elif tool_name == "update_memory": diff --git a/core/main_terminal_parts/context.py b/core/main_terminal_parts/context.py index fae15a5..8cd0e17 100644 --- a/core/main_terminal_parts/context.py +++ b/core/main_terminal_parts/context.py @@ -125,7 +125,7 @@ class MainTerminalContextMixin: detailed_rules = ( "- 所有命令和工具均可直接使用,无需用户批准\n" "- run_command:支持任意命令,包括管道、重定向、子shell等\n" - "- 文件操作:read_file / write_file / edit_file 可直接使用;其他文件管理请通过 run_command 或 run_python 执行" + "- 文件操作:read_file / write_file / edit_file 可直接使用;其他文件管理请通过 run_command 执行;需要 Python 时先探测并选择合适解释器" ) elif mode == "readonly": detailed_rules = ( @@ -138,7 +138,7 @@ class MainTerminalContextMixin: detailed_rules = ( "- run_command:先在系统只读沙箱执行;仅当出现权限拒绝时才触发审批\n" "- 审批通过后:仅当前这一次命令会重试为可写沙箱执行\n" - "- 需要用户批准:terminal_input、run_python、write_file、edit_file、save_webpage 及其他会修改工作区的操作\n" + "- 需要用户批准:terminal_input、write_file、edit_file、save_webpage 及其他会修改工作区的操作\n" "- 被拒绝或超时:本次操作不会执行写入" ) elif mode == "auto_approval": diff --git a/core/main_terminal_parts/tools_definition.py b/core/main_terminal_parts/tools_definition.py index 387bd19..3941e2b 100644 --- a/core/main_terminal_parts/tools_definition.py +++ b/core/main_terminal_parts/tools_definition.py @@ -502,7 +502,7 @@ class MainTerminalToolsDefinitionMixin: "type": "function", "function": { "name": "read_file", - "description": "读取/搜索/抽取 UTF-8 文本文件内容。通过 type 参数选择 read(阅读)、search(搜索)、extract(具体行段),支持限制返回字符数。若文件非 UTF-8 或过大,请改用 run_python。", + "description": "读取/搜索/抽取 UTF-8 文本文件内容。通过 type 参数选择 read(阅读)、search(搜索)、extract(具体行段),支持限制返回字符数。若文件非 UTF-8 或过大,请改用 run_command 调用合适的解析工具或 Python 解释器。", "parameters": { "type": "object", "properties": self._inject_intent({ @@ -809,24 +809,6 @@ class MainTerminalToolsDefinitionMixin: } } }, - { - "type": "function", - "function": { - "name": "run_python", - "description": "执行一次性 Python 脚本,可用于处理二进制或非 UTF-8 文件(如 Excel、Word、PDF、图片),或进行数据分析与验证。必须提供 timeout(最长60秒);一旦超时,脚本会被打断且无法继续执行(需要重新运行),并返回已捕获输出。", - "parameters": { - "type": "object", - "properties": self._inject_intent({ - "code": {"type": "string", "description": "Python代码"}, - "timeout": { - "type": "number", - "description": "超时时长(秒),必填,最大60" - } - }), - "required": ["code", "timeout"] - } - } - }, { "type": "function", "function": { diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index b72c29e..d805272 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -195,7 +195,6 @@ class MainTerminalToolsExecutionMixin: } _APPROVAL_REQUIRED_TOOLS = { "run_command", - "run_python", "terminal_input", "write_file", "edit_file", @@ -1407,12 +1406,6 @@ class MainTerminalToolsExecutionMixin: "path": target_path } - elif tool_name == "run_python": - result = await self.terminal_ops.run_python_code( - arguments["code"], - timeout=arguments.get("timeout") - ) - elif tool_name == "run_command": permission_mode = "unrestricted" try: diff --git a/core/tool_config.py b/core/tool_config.py index f9829a3..116da2a 100644 --- a/core/tool_config.py +++ b/core/tool_config.py @@ -66,7 +66,7 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = { ), "terminal_command": ToolCategory( label="终端指令", - tools=["run_command", "run_python"], + tools=["run_command"], ), "memory": ToolCategory( label="记忆", diff --git a/core/web_terminal.py b/core/web_terminal.py index 341239b..b9998b0 100644 --- a/core/web_terminal.py +++ b/core/web_terminal.py @@ -82,7 +82,7 @@ class WebTerminal(MainTerminal): broadcast_callback=message_callback, container_session=self.container_session ) - # 让 run_command/run_python 与实时终端共享同一容器环境 + # 让 run_command 与实时终端共享同一容器环境 self.terminal_ops.attach_terminal_manager(self.terminal_manager) print(f"[WebTerminal] 初始化完成,项目路径: {project_path}") @@ -477,12 +477,6 @@ class WebTerminal(MainTerminal): 'status': 'saving_webpage', 'detail': f'保存网页: {arguments.get("url", "")}' }) - elif tool_name == "run_python": - self.broadcast('tool_status', { - 'tool': tool_name, - 'status': 'running_code', - 'detail': '执行Python代码' - }) elif tool_name == "run_command": self.broadcast('tool_status', { 'tool': tool_name, diff --git a/docs/host_sandbox_and_permission_model.md b/docs/host_sandbox_and_permission_model.md index 717f6ea..22e5621 100644 --- a/docs/host_sandbox_and_permission_model.md +++ b/docs/host_sandbox_and_permission_model.md @@ -6,7 +6,7 @@ - 执行环境(Execution Mode) - 权限模式(Permission Mode) - 路径授权(Path Authorization) -- `run_command` / `run_python` / `terminal` / `sub_agent` 的实际行为 +- `run_command` / `terminal` / `sub_agent` 的实际行为 --- @@ -136,25 +136,20 @@ - 在 approval 模式可触发“权限拒绝 -> 审批 -> 单次可写重试” - 在 auto_approval 模式可触发“权限拒绝 -> 自动审批 -> 单次可写重试” -### 7.2 run_python - -- 复用 `run_command` 执行路径与执行环境策略 -- 与 `run_command` 一致受 sandbox/direct 与权限模式影响 - -### 7.3 terminal 系列 +### 7.2 terminal 系列 - 终端会话从启动时就在沙箱里(host+sandbox) - 同一会话中的后续输入继承该会话沙箱上下文 - 若路径授权更新,需要新开终端会话才能让该会话使用新策略 -### 7.4 子智能体(sub_agent) +### 7.3 子智能体(sub_agent) - 宿主机下已接入执行环境策略: - `sandbox`:子智能体进程经宿主机沙箱启动 - `direct`:直接宿主机启动 - Docker 模式维持容器执行 -### 7.5 自动审批(approval agent) +### 7.4 自动审批(approval agent) - 自动审批由独立审批智能体执行(与主智能体分离) - 配置文件:`config/auto_approval.json` diff --git a/modules/custom_tool_executor.py b/modules/custom_tool_executor.py index 8ae2873..8b1cc78 100644 --- a/modules/custom_tool_executor.py +++ b/modules/custom_tool_executor.py @@ -2,6 +2,7 @@ from __future__ import annotations +import shlex import string from typing import Dict, Any, Optional @@ -62,8 +63,11 @@ class CustomToolExecutor: except Exception as exc: return {"success": False, "error": f"模板渲染失败: {exc}", "tool_id": tool_id} - # 执行 python 代码 - result = await self.terminal_ops.run_python_code(rendered, timeout=timeout) + # 通过统一的 run_command 链路执行 Python,复用同一套解释器探测、沙箱与审批语义。 + result = await self.terminal_ops.run_command( + f"python3 -u -c {shlex.quote(rendered)}", + timeout=timeout, + ) result["custom_tool"] = True result["tool_id"] = tool_id result["code_rendered"] = rendered diff --git a/modules/file_manager.py b/modules/file_manager.py index 226a6da..bdf172c 100644 --- a/modules/file_manager.py +++ b/modules/file_manager.py @@ -445,7 +445,7 @@ class FileManager: except UnicodeDecodeError: return { "success": False, - "error": "文件不是 UTF-8 文本,无法直接读取,请改用 run_python 解析。" + "error": "文件不是 UTF-8 文本,无法直接读取,请改用 run_command 调用合适的解析工具或 Python 解释器。" } except Exception as e: return {"success": False, "error": f"读取文件失败: {e}"} diff --git a/modules/terminal_ops.py b/modules/terminal_ops.py index 36bf0ec..38bca9c 100644 --- a/modules/terminal_ops.py +++ b/modules/terminal_ops.py @@ -13,7 +13,6 @@ from typing import Dict, Optional, Tuple, TYPE_CHECKING from types import SimpleNamespace try: from config import ( - CODE_EXECUTION_TIMEOUT, TERMINAL_COMMAND_TIMEOUT, FORBIDDEN_COMMANDS, OUTPUT_FORMATS, @@ -25,7 +24,6 @@ except ImportError: if str(project_root) not in sys.path: sys.path.insert(0, str(project_root)) from config import ( - CODE_EXECUTION_TIMEOUT, TERMINAL_COMMAND_TIMEOUT, FORBIDDEN_COMMANDS, OUTPUT_FORMATS, @@ -111,7 +109,7 @@ class TerminalOperator: def _resolve_active_container_session(self) -> Optional[SimpleNamespace]: """ 若已存在活动终端且在容器内运行,返回一个临时的容器句柄, - 以便 run_command/run_python 复用同一个容器环境。 + 以便 run_command 复用同一个容器环境。 """ manager = self._terminal_manager if not manager: @@ -734,129 +732,6 @@ class TerminalOperator: "elapsed_ms": int((time.time() - start_ts) * 1000) } - async def run_python_code( - self, - code: str, - timeout: int = None - ) -> Dict: - """ - 执行Python代码 - - Args: - code: Python代码 - timeout: 超时时间(秒) - - Returns: - 执行结果字典 - """ - if timeout is None or timeout <= 0: - return { - "success": False, - "error": "timeout 参数必填且需大于0", - "status": "error", - "output": "timeout 参数缺失", - "return_code": -1 - } - timeout = self._clamp_timeout(timeout, default=timeout, max_limit=CODE_EXECUTION_TIMEOUT) - - # 强制重置工具容器,避免上一段代码仍在运行时输出混入 - self._reset_toolbox() - - # 创建临时Python文件 - temp_file = self.project_path / ".temp_code.py" - - try: - # 写入代码 - with open(temp_file, 'w', encoding='utf-8') as f: - f.write(code) - - print(f"{OUTPUT_FORMATS['code']} 执行Python代码") - - try: - relative_temp = temp_file.relative_to(self.project_path).as_posix() - except ValueError: - relative_temp = temp_file.as_posix() - - # 使用通用 python3 占位,由 run_command 根据执行环境重写 - result = await self.run_command( - f'python3 -u "{relative_temp}"', - timeout=timeout - ) - - # 添加代码到结果 - result["code"] = code - - return result - - finally: - # 清理临时文件 - if temp_file.exists(): - temp_file.unlink() - - async def run_python_file( - self, - file_path: str, - args: str = "", - timeout: int = None - ) -> Dict: - """ - 执行Python文件 - - Args: - file_path: Python文件路径 - args: 命令行参数 - timeout: 超时时间(秒) - - Returns: - 执行结果字典 - """ - # 构建完整路径 - full_path = (self.project_path / file_path).resolve() - - # 验证文件存在 - if not full_path.exists(): - return { - "success": False, - "error": "文件不存在", - "output": "", - "return_code": -1 - } - - # 验证是Python文件 - if not full_path.suffix == '.py': - return { - "success": False, - "error": "不是Python文件", - "output": "", - "return_code": -1 - } - - # 验证文件在项目内 - try: - full_path.relative_to(self.project_path) - except ValueError: - return { - "success": False, - "error": "文件必须在项目文件夹内", - "output": "", - "return_code": -1 - } - - print(f"{OUTPUT_FORMATS['code']} 执行Python文件: {file_path}") - - try: - relative_path = full_path.relative_to(self.project_path).as_posix() - except ValueError: - relative_path = full_path.as_posix() - - # 使用通用 python3 占位,由 run_command 根据执行环境重写 - command = f'python3 "{relative_path}"' - if args: - command += f" {args}" - - # 执行命令 - return await self.run_command(command, timeout=timeout) - async def install_package(self, package: str) -> Dict: """ 安装Python包 diff --git a/modules/toolbox_container.py b/modules/toolbox_container.py index fe49e81..64cca73 100644 --- a/modules/toolbox_container.py +++ b/modules/toolbox_container.py @@ -55,7 +55,7 @@ def _build_sandbox_options(container_session: Optional["ContainerHandle"] = None class ToolboxContainer: - """为 run_command/run_python 提供的专用容器.""" + """为 run_command 提供的专用容器.""" def __init__( self, diff --git a/prompts/main_system.txt b/prompts/main_system.txt index b0a52e5..b1e7530 100644 --- a/prompts/main_system.txt +++ b/prompts/main_system.txt @@ -20,7 +20,7 @@ | **信息检索** | 网络搜索(`web_search`)、网页内容提取(`extract_webpage`、`save_webpage`)、信息整合 | | **MCP扩展** | 通过 MCP 服务扩展外部工具;可用 `list_mcp_servers` 查看已配置服务与工具别名 | | **文件管理** | 批量处理文件、目录操作、自动化任务 | -| **终端操作** | 执行命令(`run_command`)、运行Python代码(`run_python`)、持久终端会话(`terminal_session` 系列) | +| **终端操作** | 执行命令(`run_command`,需要 Python 时先探测并选择合适解释器)、持久终端会话(`terminal_session` 系列) | | **视觉理解** | 分析图片内容、识别文字/物体/表格(`vlm_analyze`/`view_image`) | | **任务协作** | 创建子智能体(`create_sub_agent`)并行处理独立子任务 | @@ -87,7 +87,7 @@ - `write_file`:写入文件(`append` 控制覆盖/追加) - `read_file`:读取文件(支持 `read`/`search`/`extract` 三种模式) - `edit_file`:精确字符串替换(`replace_all` 必填,必须显式传入 `true/false`;`old_string` 建议至少 3 行以提升定位稳定性。需要批量替换的场景可以单行或不足一行) -- 其余文件管理(创建/重命名/删除目录与文件)请优先使用 `run_command` / `run_python` +- 其余文件管理(创建/重命名/删除目录与文件)请优先使用 `run_command` **注意**:超大文件用 `search` 定位 + `extract` 抽取,避免全文读取。 @@ -105,8 +105,7 @@ - 终端卡死需用 `terminal_session` 的 reset 操作恢复 **快速执行**: -- `run_command`:一次性命令(最长30s,输出限10000字符) -- `run_python`:执行 Python 脚本(最长60s) +- `run_command`:一次性命令(最长30s,输出限10000字符);需要 Python 时先用命令探测可用解释器(如 `python3 --version` / `python --version`)再执行 **终端禁忌**:禁止运行交互式程序(vim、python REPL等);不确定命令是否结束时,必须用 `terminal_snapshot` 确认。 diff --git a/prompts/main_system_qwenvl.txt b/prompts/main_system_qwenvl.txt index ae7abd3..5ded59a 100644 --- a/prompts/main_system_qwenvl.txt +++ b/prompts/main_system_qwenvl.txt @@ -20,7 +20,7 @@ | **信息检索** | 网络搜索(`web_search`)、网页内容提取(`extract_webpage`、`save_webpage`)、信息整合 | | **MCP扩展** | 通过 MCP 服务扩展外部工具;可用 `list_mcp_servers` 查看已配置服务与工具别名 | | **文件管理** | 批量处理文件、目录操作、自动化任务 | -| **终端操作** | 执行命令(`run_command`)、运行Python代码(`run_python`)、持久终端会话(`terminal_session` 系列) | +| **终端操作** | 执行命令(`run_command`,需要 Python 时先探测并选择合适解释器)、持久终端会话(`terminal_session` 系列) | | **视觉理解** | 自带多模态能力,可直接分析图片内容、识别文字/物体/表格/场景 | | **任务协作** | 创建子智能体(`create_sub_agent`)并行处理独立子任务 | @@ -87,7 +87,7 @@ - `write_file`:写入文件(`append` 控制覆盖/追加) - `read_file`:读取文件(支持 `read`/`search`/`extract` 三种模式) - `edit_file`:精确字符串替换(`replace_all` 必填,必须显式传入 `true/false`;`old_string` 建议至少 3 行以提升定位稳定性。需要批量替换的场景可以单行或不足一行) -- 其余文件管理(创建/重命名/删除目录与文件)请优先使用 `run_command` / `run_python` +- 其余文件管理(创建/重命名/删除目录与文件)请优先使用 `run_command` **注意**:超大文件用 `search` 定位 + `extract` 抽取,避免全文读取。 @@ -104,7 +104,7 @@ #### 细节增强与"切图放大"方法(推荐) 当图片文字很小、细节密集、或需要逐块检查时: -1. **用 `run_python` 切图**:把原图按区域裁切成若干张更小的局部图(例如:左上/右上/左下/右下;或按表格/按钮/车标/铭牌所在区域裁切) +1. **用 `run_command` 调用合适的 Python 解释器切图**:先探测可用解释器,再把原图按区域裁切成若干张更小的局部图(例如:左上/右上/左下/右下;或按表格/按钮/车标/铭牌所在区域裁切) 2. **必要时做多版本增强**:对同一区域输出多张增强版本(例如:对比度增强、锐化、灰度、二值化)用于读字/看边缘 3. **再次查看局部图**:对每张局部图分别观察并给出结论,再把结论汇总回原图任务 @@ -123,8 +123,7 @@ - 终端卡死需用 `terminal_session` 的 reset 操作恢复 **快速执行**: -- `run_command`:一次性命令(最长30s,输出限10000字符) -- `run_python`:执行 Python 脚本(最长60s,切图处理必备) +- `run_command`:一次性命令(最长30s,输出限10000字符);需要 Python/切图处理时先用命令探测可用解释器(如 `python3 --version` / `python --version`)再执行 **终端禁忌**:禁止运行交互式程序(vim、python REPL等);不确定命令是否结束时,必须用 `terminal_snapshot` 确认。 diff --git a/server/chat_flow_helpers.py b/server/chat_flow_helpers.py index e6f458e..d5b2d69 100644 --- a/server/chat_flow_helpers.py +++ b/server/chat_flow_helpers.py @@ -332,7 +332,7 @@ def detect_malformed_tool_call(text): 'create_file', 'read_file', 'write_file', 'edit_file', 'delete_file', 'terminal_session', 'terminal_input', 'web_search', 'extract_webpage', 'save_webpage', - 'run_python', 'run_command', 'sleep', + 'run_command', 'sleep', ] for tool in tool_names: if tool in text and '{' in text: diff --git a/server/chat_flow_tool_loop.py b/server/chat_flow_tool_loop.py index ea8976a..4745f60 100644 --- a/server/chat_flow_tool_loop.py +++ b/server/chat_flow_tool_loop.py @@ -120,16 +120,6 @@ def _build_tool_approval_preview(web_terminal, function_name: str, arguments: Di preview["summary"] = f"执行命令: {args.get('command') or ''}" return preview - if function_name in {"run_python", "runpython"}: - code = str(args.get("code") or "") - timeout = args.get("timeout") - preview["code"] = code - preview["timeout"] = timeout - # 兼容前端已有 command 展示分支 - preview["command"] = code - preview["summary"] = f"执行 Python 代码(timeout={timeout if timeout is not None else '未提供'}s)" - return preview - if function_name in {"create_file", "create_folder", "delete_file"}: preview["path"] = args.get("path") preview["summary"] = f"{function_name}: {args.get('path') or ''}" diff --git a/static/demo/run-python-demo.html b/static/demo/run-python-demo.html index b498462..9fb2ac9 100644 --- a/static/demo/run-python-demo.html +++ b/static/demo/run-python-demo.html @@ -3,7 +3,7 @@
-