Skip to content
Merged
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
19 changes: 10 additions & 9 deletions .github/workflows/fnos-build-fpk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ name: Build Octop FPK (template)
# # then publishes GitHub releases (fixed version + rolling latest).
# #
# # To enable on your fork:
# # 1. Replace ghcr.io/TencentCloud/octop with your own image namespace (or fork a copy).
# # 1. Replace jubaoliang/octop with your own image namespace (or fork a copy).
# # 2. Optionally add a push trigger below (e.g. branches: [main]) so a rebuild happens
# # automatically on every upstream sync. It is intentionally workflow_dispatch-only
# # here so it never runs on the upstream repository.
# #
# image job:从仓库源码构建 Octop Docker 镜像,推送到 GHCR(ghcr.io/TencentCloud/octop:vanilla)
# image job:从仓库源码构建 Octop Docker 镜像,推送到 Docker Hub(jubaoliang/octop:vanilla)
# fpk job:构建「Docker 版」飞牛安装包 Fnos-octop-vanilla-<ver>-<iter>.fpk(依赖 image,确保镜像已就绪)
# native job:构建「本地版(非 Docker)」飞牛安装包 Fnos-octop-vanilla-native-<ver>-<iter>.fpk。
# 不内置 Python 运行时(依赖飞牛应用商店 Python 3.12),
Expand Down Expand Up @@ -85,12 +85,12 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Docker Hub(与 docker-publish.yml 同一凭据,DOCKERHUB_USERNAME 为 jubaoliang 时推送 jubaoliang/octop)
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push image
uses: docker/build-push-action@v6
Expand All @@ -99,8 +99,9 @@ jobs:
file: fnos/docker/Dockerfile
push: true
tags: |
ghcr.io/TencentCloud/octop:vanilla
ghcr.io/TencentCloud/octop:vanilla-v${{ needs.version.outputs.version }}
jubaoliang/octop:latest
jubaoliang/octop:vanilla
jubaoliang/octop:vanilla-v${{ needs.version.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max

Expand Down Expand Up @@ -304,7 +305,7 @@ jobs:
echo "## 安装说明"
echo ""
echo "1. 飞牛应用中心 → 设置 → 手动安装应用,选择对应 .fpk。"
echo "2. Docker 版 **Fnos-octop-vanilla-${VER}-${ITER}.fpk**(约 80KB):飞牛自动从 GHCR 拉取镜像运行,镜像为 \\"ghcr.io/TencentCloud/octop:vanilla\\"。"
echo "2. Docker 版 **Fnos-octop-vanilla-${VER}-${ITER}.fpk**(约 80KB):飞牛自动从 Docker Hub 拉取镜像运行,镜像为 \\"jubaoliang/octop:vanilla\\"。"
echo "3. 本地版 **Fnos-octop-vanilla-native-${VER}-${ITER}.fpk**(约 200MB):非 Docker,原生运行在飞牛主机;安装时自动关联系统 Python 3.12 开发工具;浏览器、远程桌面等附加组件在 Octop 应用内按需安装。"
echo ""
echo "---"
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

## [Unreleased]

## [0.9.28] - 2026-08-26

### 修复

- 无更新权限时隐藏检查更新入口
- `/compact` 兼容 `.octop/conversation_history/` 卸载路径
- FnOS 镜像改为 Docker Hub `jubaoliang/octop`

### 新增

- 基层医生学习助手增加普通医学问答快路径、国内专业学会/专科分会与国际指南精确路由,并完善受控信源降级和检索预算。
Expand Down
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ UV := $(shell command -v uv 2>/dev/null)
RUN := $(if $(UV),uv run,)
PYTHON := $(if $(UV),uv run python,python3)
PIP := $(if $(UV),uv pip,python3 -m pip)
# Parallel workers for make test / make test-fast (override: make test PYTEST_JOBS=4)
PYTEST_JOBS ?= auto

# ─── Help ────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -219,8 +221,13 @@ typecheck:

.PHONY: test
test:
@echo "[test] pytest (not live)..."
$(RUN) pytest -m "not live"
@echo "[test] pytest (not live, -n $(PYTEST_JOBS))..."
$(RUN) pytest -n $(PYTEST_JOBS) -m "not live"

.PHONY: test-fast
test-fast:
@echo "[test-fast] pytest (not live, not slow, -n $(PYTEST_JOBS))..."
$(RUN) pytest -n $(PYTEST_JOBS) -m "not live and not slow"

.PHONY: test-live
test-live:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<p align="center">
<a href="https://www.python.org/downloads/"><img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12%2B-blue?logo=python&logoColor=white" /></a>
<a href="https://github.com/TencentCloud/Octop/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green" /></a>
<a href="https://github.com/TencentCloud/Octop/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.9.27-orange" /></a>
<a href="https://github.com/TencentCloud/Octop/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.9.28-orange" /></a>
<a href="https://pypi.org/project/octop/"><img src="https://img.shields.io/pypi/v/octop" alt="PyPI" /></a>
<a href="https://github.com/astral-sh/ruff"><img alt="Code Style: Ruff" src="https://img.shields.io/badge/code%20style-ruff-000000?logo=ruff&logoColor=white" /></a>
<a href="https://github.com/TencentCloud/Octop"><img alt="GitHub stars" src="https://img.shields.io/github/stars/TencentCloud/Octop?style=social" /></a>
Expand Down
9 changes: 5 additions & 4 deletions dashboard/src/components/AppVersionBadge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useNavigate } from "react-router-dom";
import { Tooltip } from "antd";
import { ArrowUpCircle } from "lucide-react";
import { useUpdateStatus } from "../../hooks/useUpdateStatus";
import { useUserRole } from "../../hooks/useUserRole";
import { useCurrentUser } from "../../hooks/useCurrentUser";
import { userCan } from "../../utils/permissions";
import styles from "./index.module.less";

interface AppVersionBadgeProps {
Expand All @@ -13,11 +14,11 @@ interface AppVersionBadgeProps {
export default function AppVersionBadge({ isMobile }: AppVersionBadgeProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const role = useUserRole();
const user = useCurrentUser();
const { status, hasUpdate } = useUpdateStatus();

const isAdmin = role === "admin";
if (!status?.current_version || !hasUpdate || !isAdmin) return null;
const canUpdate = userCan(user, "update");
if (!status?.current_version || !hasUpdate || !canUpdate) return null;

const tooltip = t("header.versionUpdateAvailable", {
current: status.current_version,
Expand Down
25 changes: 14 additions & 11 deletions dashboard/src/components/AvatarDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import PaletteSwitcher from "./PaletteSwitcher";
import type { OctopUser } from "../api/modules/auth";
import { useLayoutMode } from "../context/LayoutModeContext";
import type { LayoutMode } from "../layouts/layoutModeStorage";
import { userCan } from "../utils/permissions";
import styles from "./AvatarDropdown.module.less";

const GITHUB_URL = "https://github.com/TencentCloud/Octop";
Expand Down Expand Up @@ -259,17 +260,19 @@ export default function AvatarDropdown({
<span>{t("account.changePassword")}</span>
</button>

<button
type="button"
className={styles.menuItem}
onClick={() => {
setMenuOpen(false);
navigate("/admin/advanced?tab=updates");
}}
>
<RefreshCw size={16} strokeWidth={1.8} />
<span>{t("account.checkUpdates")}</span>
</button>
{userCan(user, "update") && (
<button
type="button"
className={styles.menuItem}
onClick={() => {
setMenuOpen(false);
navigate("/admin/advanced?tab=updates");
}}
>
<RefreshCw size={16} strokeWidth={1.8} />
<span>{t("account.checkUpdates")}</span>
</button>
)}

<Divider className={styles.menuDivider} />

Expand Down
11 changes: 6 additions & 5 deletions dashboard/src/components/CurrentVersionBadge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { Tooltip } from "antd";
import { useUpdateStatus } from "../../hooks/useUpdateStatus";
import { useUserRole } from "../../hooks/useUserRole";
import { useCurrentUser } from "../../hooks/useCurrentUser";
import { userCan } from "../../utils/permissions";
import styles from "./index.module.less";

interface CurrentVersionBadgeProps {
Expand All @@ -14,19 +15,19 @@ export default function CurrentVersionBadge({
}: CurrentVersionBadgeProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const role = useUserRole();
const user = useCurrentUser();
const { status } = useUpdateStatus();

const version = status?.current_version;
if (!version) return null;

const isAdmin = role === "admin";
const tooltip = isAdmin
const canUpdate = userCan(user, "update");
const tooltip = canUpdate
? t("header.currentVersionAdmin", { version })
: t("header.currentVersion", { version });
const label = `v${version}`;

if (isAdmin) {
if (canUpdate) {
return (
<Tooltip title={tooltip} mouseEnterDelay={0.35}>
<button
Expand Down
10 changes: 5 additions & 5 deletions fnos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ After install, open the app (Docker: `http://<device-ip>:8088`, native: `http://

| 版本 | 包名 | 体积 | 运行方式 | 依赖 |
|------|------|------|----------|------|
| **Docker 版** | `octop-<ver>.fpk` | ~8 KB | 飞牛自动从 `ghcr.io/TencentCloud/octop:latest` 拉镜像运行 | 宿主需有 Docker 运行时 |
| **Docker 版** | `octop-<ver>.fpk` | ~8 KB | 飞牛自动从 Docker Hub 拉取 `jubaoliang/octop:latest` 镜像运行 | 宿主需有 Docker 运行时 |
| **本地版(非 Docker)** | `octop-native-<ver>.fpk` | ~560 MB | 自带 Python 3.12 运行时 + 前端 + 全部附加组件 + Chromium,原生运行在飞牛主机 | 无需 Docker |

- **Docker 版**实现为 FnOS `docker-project`:包体只含 `docker-compose.yaml` 与向导配置,运行时由飞牛拉取 GHCR 镜像。镜像已内置全部附加组件(`browser` 浏览器自动化 + `desktop` 桌面控制)与前端。
- **Docker 版**实现为 FnOS `docker-project`:包体只含 `docker-compose.yaml` 与向导配置,运行时由飞牛从 Docker Hub 拉取镜像。镜像已内置全部附加组件(`browser` 浏览器自动化 + `desktop` 桌面控制)与前端。
- **本地版**实现为 FnOS 原生 `app`:包内自带独立 Python 3.12 运行时、Octop 全部依赖、前端构建产物、Playwright Chromium,以及 `data-share` 共享数据目录,直接以进程方式运行,不依赖 Docker。

> 两款包都在滚动发布 **`fnos-latest`**:<https://github.com/TencentCloud/Octop/releases/tag/fnos-latest>
Expand All @@ -44,7 +44,7 @@ fnos/
│ │ └── install # 安装向导(可配置管理员账号/密码、日志级别、LLM 密钥)
│ ├── app/
│ │ ├── docker/
│ │ │ └── docker-compose.yaml # 引用 ghcr.io/TencentCloud/octop:latest
│ │ │ └── docker-compose.yaml # 引用 jubaoliang/octop:latest
│ │ └── ui/
│ │ ├── config # 桌面图标入口
│ │ └── images/icon-{64,256}.png
Expand All @@ -64,7 +64,7 @@ fnos/
## 工作机制

1. **源码同步**:`.github/workflows/zz-sync-upstream.yml` 每 6 小时把上游 `TencentCloud/Octop` 的更新合并进本仓 `main` 分支(使用仓库自带 `GITHUB_TOKEN`,无需 PAT)。
2. **镜像构建**:`.github/workflows/fnos-build-fpk.yml` 的 `image` job 在 `main` 更新时,用 `fnos/docker/Dockerfile` 从仓库源码构建 Octop 镜像并推送到 `ghcr.io/TencentCloud/octop:latest`(含全部附加组件)。
2. **镜像构建**:`.github/workflows/fnos-build-fpk.yml` 的 `image` job 在 `main` 更新时,用 `fnos/docker/Dockerfile` 从仓库源码构建 Octop 镜像并推送到 Docker Hub `jubaoliang/octop:latest`(含全部附加组件)。
3. **安装包构建**:同一 workflow 的 `fpk` job 用 `scripts/build-fpk.sh` 把 `fnos/docker/` 打成 Docker 版 `.fpk`;`native` job 用 python-build-standalone 构建 Python 3.12 运行时、安装全部依赖与 Playwright Chromium,把 `fnos/native/` 打成本地版 `.fpk`(`continue-on-error`,失败不阻塞 Docker 版)。两者均以滚动发布 `fnos-latest` 提供下载。

## 本地构建 .fpk(无需 Docker)
Expand All @@ -84,6 +84,6 @@ bash scripts/build-fpk.sh native # 仅本地版 → dist/octop-native-<
- 不想依赖 Docker、希望自带运行时原生运行 → 选 `octop-native-<version>.fpk`
2. 安装向导中设置管理员账号/密码、日志级别、LLM 密钥(可选)。
3. 安装完成后桌面出现「Octop AI 助手」图标,浏览器打开 `http://<设备IP>:8088`。
4. Docker 版镜像首次会从 `ghcr.io/TencentCloud/octop:latest` 拉取;请确保该镜像为公开(workflow 已自动设为 public)。本地版无需联网拉镜像,首次启动会按需要补装 Chromium 系统库(需 root 权限,已尽力处理)。
4. Docker 版镜像首次会从 Docker Hub `jubaoliang/octop:latest` 拉取;请确保该镜像为公开(workflow 已自动设为 public)。本地版无需联网拉镜像,首次启动会按需要补装 Chromium 系统库(需 root 权限,已尽力处理)。

> 服务端口固定为 `8088`(飞牛端口映射与桌面图标均据此)。附加组件(browser 浏览器自动化 + desktop 桌面控制)已在镜像中默认安装。
2 changes: 1 addition & 1 deletion fnos/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# - desktop (mss / pynput / pillow,桌面控制附加组件)
#
# 构建上下文必须为仓库根目录(fnpack / CI 中以仓库根为 context):
# docker build -f fnos/docker/Dockerfile -t ghcr.io/TencentCloud/octop:latest .
# docker build -f fnos/docker/Dockerfile -t jubaoliang/octop:latest .
#
# 国内加速(可选 build-arg):
# PIP_INDEX_URL / PIP_TRUSTED_HOST / NPM_REGISTRY / APT_MIRROR
Expand Down
2 changes: 1 addition & 1 deletion fnos/docker/app/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
octop:
image: ghcr.io/TencentCloud/octop:latest
image: jubaoliang/octop:latest
container_name: octop
restart: unless-stopped
pull_policy: always
Expand Down
2 changes: 1 addition & 1 deletion fnos/docker/manifest
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
appname=octop
version=0.9.15
display_name=OCTOP
desc=腾讯开源的多用户多智能体 AI 助手平台,支持 Web 控制台、CLI、IM 渠道与定时任务,数据全部本地存储。<br/><br/><strong>安装后使用:</strong><br/>在应用中心点击「打开」按钮即可进入 Web 控制台,默认地址 http://设备IP:8088。<br/>初始管理员账号:admin,初始密码:Octop123(首次登录后请在头像菜单修改密码)。<br/><br/><strong>首次登录后:</strong><br/>请在控制台「设置」中配置 LLM 模型、IM 渠道、远程桌面、浏览器等组件。<br/><br/>注意:Docker 版通过容器部署,会自动从 ghcr.io/TencentCloud/octop:latest 拉取镜像运行,已预装 browser / desktop 附加组件。
desc=腾讯开源的多用户多智能体 AI 助手平台,支持 Web 控制台、CLI、IM 渠道与定时任务,数据全部本地存储。<br/><br/><strong>安装后使用:</strong><br/>在应用中心点击「打开」按钮即可进入 Web 控制台,默认地址 http://设备IP:8088。<br/>初始管理员账号:admin,初始密码:Octop123(首次登录后请在头像菜单修改密码)。<br/><br/><strong>首次登录后:</strong><br/>请在控制台「设置」中配置 LLM 模型、IM 渠道、远程桌面、浏览器等组件。<br/><br/>注意:Docker 版通过容器部署,会自动从 jubaoliang/octop:latest 拉取镜像运行,已预装 browser / desktop 附加组件。
source=thirdparty
platform=all
maintainer=TencentCloud OrcaKit
Expand Down
12 changes: 7 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "octop"
version = "0.9.27"
version = "0.9.28"
description = "Smarter self-hosted AI assistant for multiple users and agents, built on harness-agent"
readme = "README.md"
license = { text = "MIT" }
Expand All @@ -21,7 +21,7 @@ dependencies = [
"apscheduler>=3.10,<4",
"argon2-cffi>=23.1",
"pyjwt>=2.8",
"orcakit-harness-agent[all]>=0.9.26",
"orcakit-harness-agent[all]>=0.9.27",
"harness-memory>=0.9.7",
"harness-gateway>=0.9.3",
"cryptography>=41",
Expand All @@ -32,7 +32,7 @@ dependencies = [
"acme>=5.6.0",
"josepy>=2.2.0",
"boto3>=1.40.61",
"harness-browser>=0.7.5",
"harness-browser>=0.7.6",
"playwright>=1.40",
"psycopg[binary]>=3.2",
"langgraph-checkpoint-postgres>=2.0",
Expand All @@ -52,6 +52,7 @@ dev = [
"pytest-asyncio>=0.23",
"pytest-cov>=4.1",
"pytest-testmon>=2.0",
"pytest-xdist>=3.6",
"httpx>=0.27",
"ruff>=0.4",
"mypy>=1.10",
Expand All @@ -74,6 +75,7 @@ dev = [
"pytest-asyncio>=0.23",
"pytest-cov>=4.1",
"pytest-testmon>=2.0",
"pytest-xdist>=3.6",
"httpx>=0.27",
"ruff>=0.4",
"mypy>=1.10",
Expand Down Expand Up @@ -191,7 +193,8 @@ disable_error_code = ["attr-defined"]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
"live: tests that hit real LLM endpoints (skipped by default)",
"live: tests that hit real LLM / external services (skipped by default)",
"slow: expensive tests (integration boot, real harness, browser); skipped by make test-fast",
"postgresql: tests that need a live PostgreSQL (set OCTOP_TEST_DATABASE_URL)",
]
# The project pins a starlette fork (1.3.1) whose TestClient prefers a
Expand All @@ -202,4 +205,3 @@ markers = [
filterwarnings = [
"ignore::starlette.exceptions.StarletteDeprecationWarning",
]

2 changes: 1 addition & 1 deletion src/octop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

from __future__ import annotations

__version__ = "0.9.27"
__version__ = "0.9.28"
12 changes: 6 additions & 6 deletions src/octop/infra/backend/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def default_agent_backend_spec(workspace_dir: Path) -> dict[str, Any]:
"""Harness backend for agents with no explicit ``backend`` in config.

On POSIX this matches harness ``DEFAULT_BACKEND_SPEC`` (host-rooted virtual
paths). harness ``resolve_backend(..., workspace_dir=...)`` wraps that
host root in a composite whose ``artifacts_root`` is the agent workspace,
so deepagents conversation history stays writable. On Windows
``root_dir='/'`` resolves to the process current-drive root, which often
differs from the drive hosting ``workspace_dir`` — scope the default to
the agent workspace instead.
paths). harness ``resolve_backend(..., workspace_dir=..., system_files_path=...)``
wraps that host root in a composite whose ``artifacts_root`` is the agent
workspace system-files prefix (Octop: ``.octop``), so deepagents conversation
history stays with skills / sessions. On Windows ``root_dir='/'`` resolves to
the process current-drive root, which often differs from the drive hosting
``workspace_dir`` — scope the default to the agent workspace instead.
"""
from harness_agent.backends import DEFAULT_BACKEND_SPEC # noqa: PLC0415

Expand Down
10 changes: 9 additions & 1 deletion src/octop/infra/gateway/slash/handlers/composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,15 @@ async def cmd_compact(
if raw_path:
p = Path(str(raw_path))
# Prefer short workspace-relative display over absolute host paths.
short_path = f"conversation_history/{p.name}" if p.name else str(raw_path)
# Offloads land under ``{system_files_path}/conversation_history/``
# (Octop default: ``.octop/conversation_history/``).
parts = [part for part in p.parts if part not in ("/", "\\")]
if "conversation_history" in parts:
idx = parts.index("conversation_history")
start = idx - 1 if idx > 0 and parts[idx - 1].startswith(".") else idx
short_path = "/".join(parts[start:])
else:
short_path = f"conversation_history/{p.name}" if p.name else str(raw_path)
await sink.text(
tr("compact.done_offload", lang, count=count, short=tid[-6:], path=short_path)
)
Expand Down
Loading
Loading