Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@
- COS 下载的 ONNX 模型可完全离线加载,不再查询 Hugging Face API
- Docker 使用 PostgreSQL 等外部数据库时,容器重启不再重复执行初始化导致 `UniqueViolation` 崩溃:`octop init` 成功后写入数据卷初始化标记(`~/.octop/.initialized`),重启直接启动;`octop init` 对已有管理员改为幂等跳过(#1866)
- 更新提示仅对拥有「应用更新」权限的用户显示:非管理员不再看到 PWA「新版本已就绪」横幅,且关闭后同一标签页会话内不再重复弹出
- Windows 上设置 `OCTOP_HOME` 到其他磁盘时,首次创建的默认专家现在使用该目录作为后端根目录;未设置时仍使用主机文件系统根目录,用户的工作区根目录策略仍优先。
- 修复便携版程序内升级重复解析已有依赖导致超时的问题;随包提供 uv,独立启动脚本将升级安装到实际加载的 packages 目录。未带 uv 的旧包先下载应用 wheel,确认已有依赖满足新版约束后才离线安装,避免依赖不兼容时提前覆盖应用;下载和本地安装各有 90 秒预算,依赖不满足或安装后 CLI 校验失败时再完整安装依赖,跨镜像回退共用从首次完整安装开始的 900 秒计时窗口。升级校验读取实际加载的代码版本并忽略导入时的额外输出,避免 pip 遗留 dist-info 或包无法加载造成误判;同版本和旧版本在安装前拒绝(#1562)。
- Ask / Plan 模式下输入栏「+」菜单的选择面板过矮(模型、知识库一次只看得见一条),改为按视口可用高度封顶(目标 400px,且不低于左侧菜单);右侧比左侧矮时上对齐、更高时下对齐向上长;连接器/技能/专家/子智能体数量角标与知识库一样跟在文字后;模型列表现在可搜索
- 飞牛原生 start 被中途杀掉时立刻退出,不再继续就绪轮询
- 飞牛 Docker / 本地版清单版本与 pyproject 对齐为 1.0.2b6
- Docker 首次启动:未设置或不合格的 `OCTOP_DEFAULT_PASSWORD` 不再静默生成随机管理员,改为写入向导口令并走设置向导
- 云端协同:心跳检测半开连接、禁止自连/重复对端、每用户连接上限;对端回合与隧道请求不再堵住收包循环
- 自定义 OpenAI 兼容供应商默认关闭流式用量,避免华为 MindIE 等网关因 `stream_options` 返回 422
- 桌面端下载走本机 Downloads,外链与连接器授权改用系统浏览器
- Plan 模式把 `plans/*.md` 写回专家工作区,避免 POSIX 默认后端落到容器根目录 `/plans`
- 对话失败与工具错误写入可检索的服务端日志;聊天里可一键关闭实时 Token 后重试
- 英文界面残留中文文案:浏览器 AI 助手对话消息、技能录制与回放提示、PWA 安装引导、MBTI 人格标签、侧边栏更新徽标

## [1.0.2b8] - 2026-10-14

Expand Down
12 changes: 9 additions & 3 deletions src/octop/infra/agents/experts/default_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import annotations

import logging
import os
from typing import Any

from octop.infra.agents.experts.catalog import ExpertCatalog, build_create_spec_from_expert
Expand All @@ -17,6 +18,7 @@
)
from octop.infra.utils.host_dirs import host_fs_tree_root
from octop.infra.utils.locale import normalize_locale
from octop.infra.utils.paths import PathLayout

logger = logging.getLogger(__name__)

Expand All @@ -25,12 +27,16 @@


def default_home_local_backend(*, root_dir: str | None = None) -> dict[str, Any]:
"""Same local backend as the dashboard create-from-expert default.
"""Local backend for the default expert created during setup or invite.

Defaults to host filesystem root; pass *root_dir* when a user
Defaults to the configured Octop home when ``OCTOP_HOME`` is set, or
the host filesystem root otherwise. Pass *root_dir* when a user
``workspace_root_dir`` policy applies.
"""
resolved = (root_dir or "").strip() or host_fs_tree_root()
configured_home = os.environ.get("OCTOP_HOME", "").strip()
resolved = (root_dir or "").strip()
if not resolved:
resolved = str(PathLayout.from_env().root) if configured_home else host_fs_tree_root()
return {
"type": "local_shell",
"root_dir": resolved,
Expand Down
14 changes: 13 additions & 1 deletion tests/unit/agents/test_default_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,23 @@ async def test_bootstrap_main_uses_fs_root_backend(catalog: ExpertCatalog) -> No
assert spec.config["backend"]["root_dir"] == host_fs_tree_root()


async def test_bootstrap_respects_policy_root_dir(catalog: ExpertCatalog, tmp_path: Path) -> None:
def test_default_backend_uses_configured_octop_home(monkeypatch, tmp_path: Path) -> None:
octop_home = tmp_path / "octop-home"
monkeypatch.setenv("OCTOP_HOME", str(octop_home))

backend = default_home_local_backend()

assert backend["root_dir"] == str(octop_home)


async def test_bootstrap_respects_policy_root_dir(
catalog: ExpertCatalog, monkeypatch, tmp_path: Path
) -> None:
registry = MagicMock()
registry.get_row.return_value = None
registry.list_agents.return_value = []
registry.create = AsyncMock(return_value=object())
monkeypatch.setenv("OCTOP_HOME", str(tmp_path / "octop-home"))
jail = (tmp_path / "jail").resolve().as_posix()

await bootstrap_default_agent(
Expand Down