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
9 changes: 6 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ No external queue. No required services beyond an LLM provider.
| Language | Python 3.11+ |
| Web framework | FastAPI + uvicorn |
| Async runtime | asyncio (no threads except `run_in_executor`) |
| Database | SQLite via `aiosqlite` (WAL mode) |
| Database | SQLite via sync `sqlite3` (WAL) **or** PostgreSQL via `psycopg` / `psycopg_pool` |
| LLM runtime | `harness-agent` (LangGraph) at `/workspace/harness-agent` |
| Gateway | `harness-gateway` at `/workspace/harness-gateway` |
| Frontend | React 18 + TypeScript + Vite |
Expand Down Expand Up @@ -111,7 +111,7 @@ cli/ ──► launch.py ──► api/ + infra/
| `infra/backend/` | Workspace storage adapter, resolver, remote probe (COS/S3/…) | `agents/`, `api/routers/workspace*.py` |
| `infra/connectors/` | Connector catalog, OAuth, MCP gateway, credential crypto | `api/routers/connectors.py`, `internal_mcp.py`, `agents/manager.py` (MCP assembly) |
| `infra/cron/` | Cron jobs, triggers, agent tool hooks | `server.py`, `api/routers/cron.py` |
| `infra/db/` | `DBPool`, migrations, `RepoBundle` / `SharedServices` in `services.py` | all domain code needing persistence |
| `infra/db/` | `SqlitePool`, migrations, `RepoBundle` / `SharedServices` in `services.py` | all domain code needing persistence |
| `infra/gateway/` | IM ingress (`processor.py`), threads, slash commands (`slash/`), bot setup (`bot_creators/`) | `server.py`, `api/routers/chat.py`, `channels.py` |
| `infra/setup/` | First-run wizard, system service install, TLS / Let's Encrypt | `server.py`, `launch.py`, `api/routers/setup.py`, `api/routers/tls.py` |
| `infra/users/` | Users, roles, password hashing, `UserManager` | `server.py`, `api/routers/auth.py`, `users.py` |
Expand Down Expand Up @@ -211,7 +211,10 @@ make build-frontend # dashboard/ → src/octop/dashboard/

**Chat attachments:** Dashboard uploads go to `{workspace}/inbound/` via `api/common/attachments.py` + `api/routers/uploads.py`, not a separate `~/.octop/uploads/` store.

**Database:** Add columns via a new numbered migration (`infra/db/migrations/00N_description.sql`), then bump the version assertion in `tests/unit/db/test_db_pool.py` (currently `v == 1`).
**Database:** Add columns via a new numbered migration
(`infra/db/migrations/00N_description.sql` **and** matching
`00N_description.pg.sql` for PostgreSQL), then bump the version
assertion in `tests/unit/db/test_db_pool.py` (currently `v == 1`).

**Slash commands:** `infra/gateway/slash/dispatcher.py` routes; `handlers/` implements; catalog in `catalog.py`.

Expand Down
33 changes: 27 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,44 @@

## [Unreleased]

## [0.9.13] - 2026-07-23
## [0.9.14] - 2026-07-25

### 新增
- 控制平面支持 PostgreSQL 双后端(统一 DatabasePool、并行 PG 迁移、安装向导选择/绑定、pg_dump 备份;PostgreSQL 下记忆默认复用控制平面 DSN)(#60)
- SkillHub 改为走 HTTP API,支持来源中立的技能包安装与搜索 (#55)
- 远程浏览器/桌面支持真实拖拽(转发 CDP 指针事件),并共享推流连接中指示 (#50)
- 聊天界面布局与交互打磨:历史侧栏、消息队列、自动滚动与欢迎页等体验优化 (#66, #69, #70)

### 修复
- 修复 macOS/Linux 上 Agent 上下文历史写入主机根目录的问题:依赖 harness-agent≥0.9.12 将 deepagents artifacts 落到 Agent 工作区 (#57)
- Provider catalog 的 `context_window` 映射为 harness `max_input_tokens`,修复 Auto/摘要阈值与 UI 上下文环按错误上限计算的问题
- 元宝扫码绑定后保存官方 API 与 WebSocket 地址,并升级网关至 0.8.7 以支持完整媒体收发 (#56)
- ChatGPT/Codex OAuth 改为 device code 流程,修复非 localhost 部署下授权失败 (#54)
- 技能 CLI 安装不再根据用户输入的 slug 推导路径,避免装错包 (#63)
- 删除会话时同步清理 harness checkpoint,避免「删除」后消息历史仍残留 (#60)
- 修正 PostgreSQL 记忆可移植导出的误导性 pg_dump 提示(共享 schema 下按 namespace 隔离,不可整库导出单 agent)(#60)
- 技能启用/禁用与 SkillHub 安装不再触发整机 Agent rebuild,避免切到技能列表时短暂「未找到 Agent」
- 修复聊天向上滚动加载更早消息失效,并在列表未溢出时提供可点击回退
- 工作区路径语义澄清(`from_workspace`),并加固 Windows 下 file URL / 主机路径校验

### 变更
- `/compact` 改为在当前话题强制触发一次 Summarization(总结较早消息并 offload 到 `conversation_history/`),不再新建线程;新建空话题请用 `/new`
- `/compact` 成功提示明确:聊天界面仍保留完整历史,压缩的是下一轮模型可见上下文
- 文档与发布流程改为 develop 日常集成、先合入 main 再打 tag (#48)

## [0.9.13] - 2026-07-23

### 新增
- SkillHub 改为走 HTTP API,支持来源中立的技能包安装与搜索 (#55)
- 远程浏览器/桌面支持真实拖拽(转发 CDP 指针事件),并共享推流连接中指示 (#50)

### 修复
- 修复 macOS/Linux 上 Agent 上下文历史写入主机根目录的问题:依赖 harness-agent≥0.9.12 将 deepagents artifacts 落到 Agent 工作区 (#49, #57)
- Provider catalog 的 `context_window` 映射为 harness `max_input_tokens`,修复 Auto/摘要阈值与 UI 上下文环按错误上限(如 128k)计算的问题
- 修复取消聊天任务后再次提问会一直停留在思考状态的问题 (#42, #43)
- 技能启用/禁用与 SkillHub 安装不再触发整机 Agent rebuild,避免切到技能列表时短暂「未找到 Agent」
- 内置专家卡片标题与图标水平对齐
- SkillHub / 专家市场在 Python SSL 失败时给出可操作提示,并修正技能市场错误态「Retry」未本地化为「刷新」(#46)
- SkillHub / 专家市场在 Python SSL 失败时给出可操作提示,并修正技能市场错误态「Retry」未本地化为「刷新」(#44, #46)
- 元宝扫码绑定后保存官方 API 与 WebSocket 地址,并升级网关至 0.8.7 以支持完整媒体收发 (#56)
- ChatGPT/Codex OAuth 改为 device code 流程,修复非 localhost 部署下授权失败 (#54)
- 远程桌面安装拒绝不支持的 EL10 环境 (#41)
Expand All @@ -30,10 +55,6 @@
- `/compact` 改为在当前话题强制触发一次 Summarization(总结较早消息并 offload 到 `conversation_history/`),不再新建线程;新建空话题请用 `/new`
- `/compact` 成功提示明确:聊天界面仍保留完整历史,压缩的是下一轮模型可见上下文
- 文档与发布流程改为 develop 日常集成、先合入 main 再打 tag (#48)
- 修复取消聊天任务后再次提问会一直停留在思考状态的问题 (#42)
- 技能启用/禁用与 SkillHub 安装不再触发整机 Agent rebuild,避免切到技能列表时短暂「未找到 Agent」
- 内置专家卡片标题与图标水平对齐
- SkillHub / 专家市场在 Python SSL 失败时给出可操作提示,并修正技能市场错误态「Retry」未本地化为「刷新」(#44)

## [0.9.12] - 2026-07-21

Expand Down
6 changes: 3 additions & 3 deletions 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.11+" src="https://img.shields.io/badge/python-3.11%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.13-orange" /></a>
<a href="https://github.com/TencentCloud/Octop/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.9.14-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 Expand Up @@ -358,7 +358,7 @@ See [docs/configuration.md](docs/configuration.md) for env vars and `config.json

```
OctopServer
├─ DBPool SQLite (WAL mode)
├─ SqlitePool SQLite (WAL mode)
├─ SharedServices DI root — every repo + config
├─ ExpertCatalog scans agents/experts/library/ at boot
├─ UserManager
Expand Down Expand Up @@ -402,7 +402,7 @@ make install # pip install -e ".[dev]"
make all # lint + typecheck + test (ship bar)

# Frontend (separate terminal)
make dev-frontend # Vite dev server on :5173
make dev-frontend # Vite dev server on :5173 (override with VITE_DEV_PORT)
make build-frontend # production build → src/octop/dashboard/
cd dashboard && npx tsc --noEmit
```
Expand Down
15 changes: 9 additions & 6 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
| **Web 框架** | FastAPI + uvicorn |
| **Agent 运行时** | harness-agent |
| **IM 桥接** | harness-gateway |
| **控制平面数据库** | SQLite (WAL) + aiosqlite |
| **控制平面数据库** | SQLite (WAL,默认) 或 PostgreSQL(可选) |
| **前端** | React 18 + TypeScript + Vite + Ant Design |
| **调度** | APScheduler |
| **ACP** | agent-client-protocol |
Expand All @@ -88,7 +88,7 @@ Octop 基于一系列 Harness 工程实践构建——它将这些专注的运
- **harness-memory** — 分层记忆与全文检索,让 Agent 的记忆随工作区一同迁移。
- **harness-browser** — 基于 CDP 的浏览器自动化,支持持久化配置,用于网页类任务。

Octop 不依赖外部消息队列或中间件,而是通过进程内的 `HarnessProcessor` 统一路由所有入口——Web UI、IM 与定时任务。最终呈现为一个可重启恢复的单进程:启动时整个状态都从 `~/.octop/octop.db` 重建
Octop 不依赖外部消息队列或中间件,而是通过进程内的 `HarnessProcessor` 统一路由所有入口——Web UI、IM 与定时任务。最终呈现为一个可重启恢复的单进程:启动时整个状态都从控制面数据库重建(默认 `~/.octop/octop.db`,亦可配置 PostgreSQL)

## 🤔 功能特性

Expand Down Expand Up @@ -349,7 +349,8 @@ OpenAI 兼容 API、DashScope(千问)、Ollama 等预设 — 在控制台或

```
~/.octop/ ← 安装与数据根目录
├── octop.db # SQLite — 用户、Agent、通道、定时任务 …
├── config.json # 进程配置(含可选 database 段)
├── octop.db # 默认 SQLite 控制面(用户、Agent、通道、定时任务 …)
├── secrets/ # JWT 密钥、通道 Token
├── agents/<agent_id>/ # 各 Agent 工作区(SOUL.md、skills …)
├── security/tool_guard/ # Shell 命令允许/拒绝规则
Expand All @@ -358,13 +359,15 @@ OpenAI 兼容 API、DashScope(千问)、Ollama 等预设 — 在控制台或
└── bin/octop # PATH 包装脚本 → venv/bin/octop
```

控制面也可改用 PostgreSQL(`config.json` → `database`,或 `OCTOP_DATABASE_*` / 首次设置向导)。控制面为 PostgreSQL 时,Agent 记忆**默认复用同一 DSN**(per-agent schema);若需继续用文件记忆,在 agent 配置里设 `"memory": { "backend": { "type": "sqlite" } }`。详见 [docs/configuration.md](docs/configuration.md) 与 [docs/adr/002-database-backends.md](docs/adr/002-database-backends.md)。

环境变量与 `config.json` 详见 [docs/configuration.md](docs/configuration.md)。

### 🏗️ 架构

```
OctopServer
├─ DBPool SQLite (WAL 模式)
├─ DatabasePool SQLite (WAL) 或 PostgreSQL
├─ SharedServices 依赖注入根 — 所有 repo 与配置
├─ ExpertCatalog 启动时扫描 agents/experts/library/
├─ UserManager
Expand All @@ -377,9 +380,9 @@ OctopServer
└─ FastAPI app (uvicorn)
```

单进程架构。重启后从 `~/.octop/octop.db` 重建状态
单进程架构。重启后从控制面数据库重建状态(默认本地 SQLite;可选 PostgreSQL)

详见 [docs/architecture.md](docs/architecture.md)[docs/adr/001-single-process-model.md](docs/adr/001-single-process-model.md)。
详见 [docs/architecture.md](docs/architecture.md)[docs/adr/001-single-process-model.md](docs/adr/001-single-process-model.md) 与 [docs/adr/002-database-backends.md](docs/adr/002-database-backends.md)。

### 📁 项目结构

Expand Down
Binary file added dashboard/public/octop-mascot-peek.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dashboard/public/octop-mascot-type.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions dashboard/src/api/modules/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export interface AuthStatus {
wizard_password_required: boolean;
/** Absolute path to the one-time bootstrap password file on the server. */
wizard_password_path?: string;
/** True after `/setup/database` has bound the control-plane pool. */
database_bound: boolean;
/** Active control-plane driver when bound (`sqlite` | `postgresql`). */
database_driver?: string | null;
}

export interface OctopUser {
Expand Down Expand Up @@ -62,6 +66,8 @@ interface RawSetupStatus {
wizard_password_required?: boolean;
wizard_password_exists?: boolean;
wizard_password_path?: string;
database_bound?: boolean;
database_driver?: string | null;
}

interface RawLoginResponse {
Expand All @@ -83,6 +89,8 @@ export const authApi = {
wizard_password_exists: raw.wizard_password_exists ?? false,
wizard_password_required: raw.wizard_password_required ?? true,
wizard_password_path: raw.wizard_password_path,
database_bound: raw.database_bound ?? false,
database_driver: raw.database_driver ?? null,
};
},

Expand Down
38 changes: 38 additions & 0 deletions dashboard/src/api/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,44 @@ export async function requestBlob(
return response.blob();
}

/**
* Authenticated GET that only checks success — cancels the body without
* buffering it (existence probes for large workspace files).
*/
export async function probeAuthResource(
path: string,
options: RequestInit = {},
): Promise<void> {
const url = getApiUrl(path);
const headers = buildAuthHeaders(path);
const response = await fetch(url, {
...options,
headers: { ...headers, ...(options.headers as Record<string, string>) },
});

if (await check503ForSetupRequired(path, response)) {
throw new Error("Setup required — redirecting to /setup");
}

await throwIfUnauthorized(path, response);
applyRenewedAccessToken(response);

if (!response.ok) {
const text = await response.text().catch(() => "");
throw new Error(
`Request failed: ${response.status} ${response.statusText}${
text ? ` - ${text}` : ""
}`,
);
}

try {
await response.body?.cancel();
} catch {
/* ignore cancel failures */
}
}

export type UploadProgressHandler = (percent: number) => void;

/**
Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/components/AgentProfileDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ export default function AgentProfileDrawer({
rootClassName={styles.mobileDrawer}
styles={mobileDrawerStyles}
onClose={handleDrawerClose}
destroyOnClose
destroyOnHidden
>
{viewingFile ? renderFileContent() : renderProfileContent()}
</Drawer>
Expand Down Expand Up @@ -488,7 +488,7 @@ export default function AgentProfileDrawer({
title={t("chat.agentProfile.title")}
width={420}
onClose={onClose}
destroyOnClose
destroyOnHidden
>
{renderProfileContent()}
</Drawer>
Expand All @@ -498,7 +498,7 @@ export default function AgentProfileDrawer({
title={viewingFile ? displayName(viewingFile) : ""}
width={480}
onClose={closeFileView}
destroyOnClose
destroyOnHidden
>
{renderFileContent()}
</Drawer>
Expand Down
25 changes: 11 additions & 14 deletions dashboard/src/components/AppVersionBadge/index.module.less
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
.headerVersion {
display: inline-flex;
align-items: center;
padding: 3px 8px;
justify-content: center;
width: auto;
height: auto;
padding: 0;
margin: 0;
border: none;
border-radius: 999px;
font-size: 10px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.02em;
color: #fff;
background: #ff4d4f;
white-space: nowrap;
border-radius: 0;
color: var(--fn-color-error, #ff4d4f);
background: transparent;
flex-shrink: 0;
cursor: default;
transition:
background 0.15s ease,
filter 0.15s ease;
color 0.15s ease,
opacity 0.15s ease;
}

.headerVersionMobile {
font-size: 10px;
padding: 2px 7px;
/* size handled by lucide icon prop */
}

.headerVersionClickable {
cursor: pointer;

&:hover {
filter: brightness(1.08);
opacity: 0.85;
}
}
5 changes: 3 additions & 2 deletions dashboard/src/components/AppVersionBadge/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useTranslation } from "react-i18next";
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 styles from "./index.module.less";
Expand All @@ -25,7 +26,7 @@ export default function AppVersionBadge({ isMobile }: AppVersionBadgeProps) {
});

const handleClick = () => {
navigate("/admin/updates");
navigate("/admin/advanced?tab=updates");
};

return (
Expand All @@ -38,7 +39,7 @@ export default function AppVersionBadge({ isMobile }: AppVersionBadgeProps) {
onClick={handleClick}
aria-label={tooltip}
>
{t("header.newVersionBadge", "有新版本")}
<ArrowUpCircle size={isMobile ? 14 : 15} strokeWidth={2} />
</button>
</Tooltip>
);
Expand Down
Loading
Loading