Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b07594e
Merge pull request #307 from TencentCloud/chore/sync-develop-after-ma…
github-actions[bot] Aug 15, 2026
07259e4
feat(skills): allow Unicode skill names with filesystem-safe validation
chujieHong Aug 16, 2026
0f8e7b4
fix(chat): strip media-offload placeholder text from image history
chujieHong Aug 14, 2026
420d5e2
fix(dashboard): prevent AGENT_NOT_RUNNING race when saving page confi…
chujieHong Aug 17, 2026
961b572
fix(chat): stop message-list jitter while typing during streaming (#303)
chujieHong Aug 17, 2026
f3e2415
fix(plugins): sanitize non-ASCII tool names for strict LLM tool-name …
chujieHong Aug 17, 2026
10012e1
Feat/自定义主题色 (#334)
miaowmint Aug 19, 2026
aa8759e
fix(voice): MiMo TTS low-latency streaming, voice preset fix, TTS aut…
miaowmint Aug 19, 2026
37f612e
Feat/meituan living assistant expert 内置美团生活助手专家 (#328)
miaowmint Aug 19, 2026
1b5f18c
fix(dashboard): adjust CronJobs table layout for better content displ…
liukewia Aug 19, 2026
89f6188
fix(dashboard): move update progress/result prompts above release not…
miaowmint Aug 19, 2026
90621a8
feat: show memory slimming progress on chat and agent status (#321)
Bosheng0422 Aug 19, 2026
1829098
fix(chat): hide stale scroll-to-bottom button after dock close (#304)
chujieHong Aug 19, 2026
94cf7bd
feat(experts): allow custom agent id when creating experts (#333)
miaowmint Aug 19, 2026
8fc95fa
fix(channels): add missing internal scroll to channels panel in fill …
chujieHong Aug 19, 2026
de606bb
feat(expert): extend meituan-living-assistant to 6 quick prompts (#344)
miaowmint Aug 19, 2026
0dce85c
fix: inject Admin env into running agents and sum turn token usage (#…
jubaoliang Aug 19, 2026
9b3aeea
fix(dashboard): hide cron session routing when using a fresh thread (…
jubaoliang Aug 19, 2026
e07824e
Fix/agent env usage workspace (#351)
jubaoliang Aug 20, 2026
454048b
fix(gateway): upgrade to 0.9.3 (#353)
Pandakingxbc Aug 20, 2026
8c21334
fix(usage): add cache-aware token accounting (#352)
Pandakingxbc Aug 20, 2026
afd1857
fix(cli): force utf-8 stdio so octop init does not crash on GBK conso…
c020627 Aug 20, 2026
248dc4d
修复onnx向量模型调用 (#360)
Bosheng0422 Aug 20, 2026
58356a9
fix(skills): keep sibling files when editing a skill's SKILL.md (#365)
chujieHong Aug 20, 2026
389df18
Feat/tencentcloud api expert (#370)
miaowmint Aug 21, 2026
7abaf6b
feat(experts): add clinical learning subscription assistant (#369)
Pandakingxbc Aug 21, 2026
18ab9f6
feat: workspace backend sandboxing, execute-env injection, and epheme…
jubaoliang Aug 21, 2026
d884c56
chore: release 0.9.25
jubaoliang-tencent Aug 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ src/octop/dashboard/assets/*
!src/octop/dashboard/__init__.py
!src/octop/dashboard/.gitkeep

# 美团生活助手专家:随包分发 pt-passport 运行时(Docker 镜像无 npm,
# 依赖此预装产物执行认证;包内文件由来源 tgz 固定,不会被误提交污染)
# 注意:git 不会进入被忽略的目录,因此必须先反排除 node_modules 目录本身
!src/octop/infra/agents/experts/library/meituan-living-assistant/
!src/octop/infra/agents/experts/library/meituan-living-assistant/skills/
!src/octop/infra/agents/experts/library/meituan-living-assistant/skills/meituan-deals/
!src/octop/infra/agents/experts/library/meituan-living-assistant/skills/meituan-deals/scripts/
!src/octop/infra/agents/experts/library/meituan-living-assistant/skills/meituan-deals/scripts/node_modules/
!src/octop/infra/agents/experts/library/meituan-living-assistant/skills/meituan-deals/scripts/node_modules/**

# Local databases
*.sqlite
*.sqlite-journal
Expand Down
35 changes: 31 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ make build-frontend # dashboard/ → src/octop/dashboard/

**Agent workspace I/O:** All reads/writes of agent workspace **content files** go through `HarnessAgent.workspace` (`BackendWorkspace` from `harness-agent`). Do **not** use `agent.backend` directly, `resolve_harness_backend`, or `Path.write_text` / `read_text` on `~/.octop/agents/<id>/` for workspace content. Do **not** branch on backend type or `virtual_mode` in Octop — path rules live in `BackendWorkspace`.

New agents additionally keep system-scoped files under `{workspace}/.octop/` (e.g. sessions/sqlite, skills/agents system trees, auth tokens under `.octop/auth`). `system_files_path` is internal and not user-configurable; the `.octop` directory is created on first harness workspace init. Legacy agents keep auth at `{workspace}/.octop-auth`.

| Context | Entry |
|---------|--------|
| HTTP (agent must be running) | `require_running_workspace()` in `api/common/workspace.py` |
Expand All @@ -216,10 +218,35 @@ 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` **and** matching
`00N_description.pg.sql` for PostgreSQL), then bump the version
assertion in `tests/unit/db/test_db_pool.py` (currently `v == 1`).
**Database:** SQLite and PostgreSQL share one schema. Add or change tables via a numbered pair
`infra/db/migrations/00N_description.sql` **and** `00N_description.pg.sql`, then bump the
version assertion in `tests/unit/db/test_db_pool.py` (currently `v == 7`). Rebuilds that SQLite
cannot express as `ALTER` live in `infra/db/migrate.py` helpers and must stay idempotent.

Unreleased schema work on `develop` **folds into the current unreleased `00N`**, not a new
`00N+1`. Only cut a new number after that version has shipped (or after a local DB at `N` would
otherwise skip the change). `_schema_version` is an applied-migration watermark, not a changelog
of every edit. The numbered SQL pair is the canonical v(N-1)→vN DDL (including table rebuilds);
`migrate.py` helpers must stay equivalent and idempotent for SQLite `ADD COLUMN` and for
databases whose recorded version skipped the file. Do not re-apply a RENAME rebuild just to
clamp `_schema_version`.

Resource tables (API-visible entities: `agents`, `channels`, `threads`, `cron_jobs`,
`knowledge_bases`, `knowledge_documents`, `skill_packages`, `published_experts`, …) use:

| Column | Role |
|--------|------|
| `id` | Integer surrogate PK (`AUTOINCREMENT` / `GENERATED BY DEFAULT AS IDENTITY`) |
| `{entity}_id` | Public string UNIQUE (ULID / short id). APIs, paths, and other tables use this |

Child rows store the **string** id (`knowledge_base_id` or a short alias such as `kb_id`,
`agent_id`, `thread_id`) and `REFERENCES parent({entity}_id)`. Never FK the integer `id`.
Repos map `row.id` to the public string; keep the integer as `row.pk` when both are needed.

Do **not** force this onto: `users` (integer `user_id` FKs; login identity is `username`);
name-keyed config (`providers`, `voice_providers`, `storage_backends`); append-only logs
(`usage_log`, `audit_log`, `care_push_records`); KV (`settings`, `secrets`); 1:1 extensions
(`proactive_care_config` keyed by `agent_id`); ephemeral rows (`sso_login_states`).

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

Expand Down
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,32 @@

## [Unreleased]

## [0.9.25] - 2026-08-21

### 新增

- Token 计量新增缓存命中支持:按模型调用累计未缓存输入、缓存读取、缓存写入、推理 Token 与模型调用次数;用量页和消息气泡展示缓存命中数据。
- 编辑专家抽屉可修改标题语(欢迎语),与创建时同一字段,写入智能体实例而非仅页面配置
- 专家支持上传自定义头像,写入工作区 `.octop/avatar.png`(或 jpg/webp/gif)并通过 `agents.icon_url` 展示;发布快照会带上头像,安装后自动绑定。未设置时仍用配色 + Lucide 图标
- 实例化专家的欢迎语改为单一 `welcome_message` 字段(用户自填,不再分中英);专家模板仍保留双语欢迎词
- 智能体状态接口返回 `memory_maintenance`(queued / pruning / compacting)。聊天页显示阶段进度条,整理本库时暂停发送;专家卡片显示「整理记忆」标签。
- 聊天页:文件工具卡片显示「编辑了 N 个文件」(不含截图);文件面板标题改为「文件变更」。会话 `artifacts` 由 Octop 工具中间件在写文件 / 发文件 / 桌面截图成功后写入,切换会话后仍可在文件变更中查看。路径优先取工具 args,仅在 args 没有 path 时才扫结果文本。专家选择器「共享」标记与名称同一行。右侧增加工作区入口;工作区目录树支持将文件拖到其他文件夹。
- 知识库、技能包、已发布专家改为整数自增 `id` + 对外字符串 ID(`knowledge_base_id` / `skill_package_id` / `published_expert_id`;文档用 `kb_id` 关联)。`agents` 仍用字符串引用技能包与已发布专家。知识库文档支持文件夹路径;分片大小等仍存实例 `settings`。删除未使用的 `knowledge_base_members`。上述库变更与专家资料列、会话 artifacts、实例欢迎语单字段一并作为 schema v7。
- 对话检索结果附带知识库引用标记;聊天页在回答下方展示可点击的来源文档(跳转知识库页)

### 修复

- 技能:修复编辑已导入技能并保存后,技能内其余文件与文件夹(README.md、references/ 等)被整体清除的问题——内容编辑(仅 SKILL.md)现原地覆盖清单文件、保留全部同级文件;携带完整 `files` 载荷的更新仍整目录替换(与覆盖重装语义一致)
- Harness usage 事件按稳定调用 ID 去重,避免流重放重复计费;上下文环保留路由模型的真实窗口上限,并将构成拆分明确显示为近似估算。
- Admin 环境变量未进入正在运行的 Agent:本地 shell 默认不继承进程 env,Docker exec 也不传 env,工作区 `.env` 只落盘不注入。现本地 shell 每次 execute 继承当前进程环境(含 `~/.octop/env`)并 overlay 工作区 `.env`;Docker 热读全局文件 + 工作区 `.env` + 最小 PATH(不含完整宿主机环境)。保存 Admin 列表会从进程环境删除已去掉的键;仅搜索类 key 变化时后台 reload Agent。MCP stdio 只注入 SDK 安全子集 + 全局/连接器 env,不再灌入整份 `os.environ`。
- Token 用量账本每轮只记下最后一次模型调用,工具循环中前面若干次调用被丢弃,页面合计会远低于聊天里看到的用量;现按该轮全部 AI 调用的 `usage_metadata` 累加,以 `state_snapshot` 为权威终值(snapshot 之后的增量不再相加),畸形 usage 字段跳过以免打断对话
- 插件工具使用中文等非 ASCII 名称时 LLM 调用失败:主流 API 要求工具名匹配 `^[a-zA-Z0-9_-]{1,64}$`,现自动将非法名称转写为合法拼音名(`pypinyin` 缺失时退回下划线替换),冲突追加 `_2`/`_3` 后缀,并在工具描述前缀 `[原名: …]` 保留原名映射;`config_json.plugins` 配置键与插件内部仍使用原始名称,路由不受影响
- 修复聊天页在"生成中"时于输入框持续打字导致消息列表上下轻微抖动的问题:输入框高度测量改为在离屏克隆节点上进行,不再瞬态改变页面布局
- 工作区读写在 harness 后台重建窗口(DB 仍为 running)回退到 `workspace_for_agent`,避免误报 `AGENT_NOT_RUNNING`
- 聊天页上下文占用环:旧会话没有分段快照时,从消息上已有的 `usage_metadata` / `response_metadata.token_usage` 回填已用量;相对 1M 级窗口不再把真实占用四舍五入成 0%
- 专家抽屉保存页面配置时合并写入 `manifest.json`:保留其它字段与另一语言欢迎语;加载未完成或未改动时不写文件
- 修复个性化「通道」面板在页面放大后不出现纵向滚动条、被挤出的通道卡片无法查看的问题:工具栏固定、卡片网格改为内部滚动区(与技能面板一致),移动端仍整页滚动

## [0.9.24] - 2026-08-15

### 新增
Expand All @@ -17,7 +43,7 @@
- 技能:新增对话式技能管理器(SkillHub),并兼容 Windows
- 备份:新增自动定时系统备份
- 频道:新增 final-only 仅终稿回复模式
- 线程:支持会话线程分叉(fork)
- 线程:支持从 AI 回复处分叉会话(fork)
- 体验:HITL 工具选择器、运行时按需安装、KB/技能 UX 优化;对话与镜像等界面打磨;知识嵌入初始化流程加固

### 修复
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.24-orange" /></a>
<a href="https://github.com/TencentCloud/Octop/releases"><img alt="Version" src="https://img.shields.io/badge/version-0.9.25-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
8 changes: 5 additions & 3 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import MainLayout from "./layouts/MainLayout";
import LoginPage from "./pages/Login";
import OidcComplete from "./pages/Login/OidcComplete";
import SetupPage from "./pages/Setup";
import InvitePage from "./pages/Invite";
import AuthGuard from "./components/AuthGuard";
import { AntdAppProvider } from "./components/AntdAppProvider";
import GlobalErrorBoundary from "./components/ErrorBoundary";
Expand All @@ -15,7 +16,7 @@ import { AgentProvider } from "./context/AgentContext";
import { VoiceOutputProvider } from "./context/VoiceOutputContext";
import { useIsMobile } from "./hooks/useIsMobile";
import { useUnauthorizedRedirect } from "./hooks/useUnauthorizedRedirect";
import { ANTD_BRAND_TOKENS } from "./styles/themePalettes";
import { brandTokensFor } from "./styles/themePalettes";
import "./styles/theme-vars.css";
import "./styles/layout.css";
import "./styles/form-override.css";
Expand All @@ -28,10 +29,10 @@ const GlobalStyle = createGlobalStyle`
`;

function ThemedApp() {
const { isDark, palette } = useTheme();
const { isDark, palette, customColor } = useTheme();
const { t } = useTranslation();
const isMobile = useIsMobile();
const brandTokens = ANTD_BRAND_TOKENS[palette][isDark ? "dark" : "light"];
const brandTokens = brandTokensFor(palette, isDark, customColor);

useUnauthorizedRedirect();

Expand Down Expand Up @@ -102,6 +103,7 @@ function ThemedApp() {
<Route path="/login" element={<LoginPage />} />
<Route path="/login/oidc/complete" element={<OidcComplete />} />
<Route path="/setup" element={<SetupPage />} />
<Route path="/invite" element={<InvitePage />} />
<Route
path="/*"
element={
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/api/modules/expertMarket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface CreateMarketExpertResponse {
state: string;
expert_id: string;
icon_name?: string | null;
icon_url?: string | null;
color?: string | null;
market: {
source: string;
Expand All @@ -63,6 +64,7 @@ export interface CreateMarketExpertBody {
backend?: Record<string, unknown>;
skill_package_ids?: string[];
color?: string;
welcome_message?: string;
max_iters?: number | null;
max_input_length?: number | null;
temperature?: number | null;
Expand Down
75 changes: 75 additions & 0 deletions dashboard/src/api/modules/invites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { request } from "../request";

export type InviteStatus = "pending" | "used" | "expired" | "revoked";

export interface InviteRow {
id: number;
code: string;
note: string | null;
created_by: number;
created_at: number;
expires_at: number;
used_at: number | null;
used_by_user_id: number | null;
revoked_at: number | null;
status: InviteStatus;
invite_path: string;
invite_url: string;
}

export interface InviteCreateBody {
note?: string | null;
expires_in_days?: number;
}

export interface InviteRedeemBody {
code: string;
username: string;
password: string;
display_name?: string | null;
}

export interface InviteRedeemResponse {
access_token: string;
token_type: string;
expires_in: number;
user: {
id: number;
username: string;
role: "admin" | "user";
display_name: string | null;
locale: string;
permissions?: string[];
};
}

/** Prefer the browser origin so copied links match how admins reach the UI. */
export function localInviteUrl(code: string): string {
const origin = typeof window !== "undefined" ? window.location.origin : "";
return `${origin}/?invite=${encodeURIComponent(code)}`;
}

export const invitesApi = {
list: () => request<InviteRow[]>("/users/invites"),

create: (body: InviteCreateBody = {}) =>
request<InviteRow>("/users/invites", {
method: "POST",
body: JSON.stringify(body),
}),

revoke: (id: number) =>
request<InviteRow>(`/users/invites/${id}/revoke`, { method: "POST" }),

validate: (code: string) =>
request<{ ok: boolean; expires_at: number }>("/auth/invite/validate", {
method: "POST",
body: JSON.stringify({ code }),
}),

redeem: (body: InviteRedeemBody) =>
request<InviteRedeemResponse>("/auth/invite/redeem", {
method: "POST",
body: JSON.stringify(body),
}),
};
35 changes: 32 additions & 3 deletions dashboard/src/api/modules/knowledgeBases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export interface KnowledgeCapability {

export interface KnowledgeBase {
id: string;
knowledge_base_id?: string;
pk?: number;
owner_user_id: number;
owner_username?: string | null;
owner_display_name?: string | null;
Expand All @@ -41,8 +43,11 @@ export interface KnowledgeBase {

export interface KnowledgeDocument {
id: string;
document_id?: string;
kb_id: string;
path?: string;
filename: string;
is_dir?: boolean;
content_type: string;
byte_size: number;
content_hash: string;
Expand Down Expand Up @@ -118,6 +123,17 @@ export const knowledgeBasesApi = {
"/knowledge-bases/onnx-download-status",
),

testOnnx: (model: string) =>
request<{
ok: boolean;
latency_ms?: number | null;
dim?: number | null;
error?: string | null;
}>("/knowledge-bases/onnx-test", {
method: "POST",
body: JSON.stringify({ model }),
}),

activateOnnx: (model: string) =>
request<{ enabled: boolean; model: string; ready: boolean }>(
"/knowledge-bases/onnx-activate",
Expand Down Expand Up @@ -159,12 +175,25 @@ export const knowledgeBasesApi = {
delete: (id: string) =>
request<void>(`/knowledge-bases/${id}`, { method: "DELETE" }),

listDocuments: (id: string) =>
request<KnowledgeDocument[]>(`/knowledge-bases/${id}/documents`),
listDocuments: (id: string, prefix?: string) =>
request<KnowledgeDocument[]>(
prefix
? `/knowledge-bases/${id}/documents?prefix=${encodeURIComponent(
prefix,
)}`
: `/knowledge-bases/${id}/documents`,
),

createFolder: (id: string, path: string) =>
request<KnowledgeDocument>(`/knowledge-bases/${id}/folders`, {
method: "POST",
body: JSON.stringify({ path }),
}),

uploadDocument: (id: string, file: File) => {
uploadDocument: (id: string, file: File, relativePath?: string) => {
const body = new FormData();
body.append("upload", file);
if (relativePath) body.append("path", relativePath);
return requestUpload<KnowledgeDocument>(
`/knowledge-bases/${id}/documents`,
body,
Expand Down
11 changes: 11 additions & 0 deletions dashboard/src/api/modules/memoryDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ export interface RejectCandidateResponse {
status: "rejected";
}

export interface LastExtractRun {
timestamp?: string;
session_id?: string | null;
quiet?: boolean;
note?: string;
events_extracted?: number;
candidates?: number;
failure_reason?: string | null;
}

export interface StatsCounts {
raw_events: number;
atoms: number;
Expand All @@ -247,6 +257,7 @@ export interface StatsCounts {
atoms_delta_7d: number;
entities_delta_7d: number;
episodes_delta_7d: number;
last_extract_run?: LastExtractRun | null;
}

export interface StatsAtomKindsResponse {
Expand Down
16 changes: 15 additions & 1 deletion dashboard/src/api/modules/octopAgents.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { request } from "../request";
import { request, requestUpload } from "../request";

export const octopAgentsApi = {
markRead: (agentId: string) =>
request<void>(`/agents/${encodeURIComponent(agentId)}/read`, {
method: "POST",
}),

uploadAvatar: (agentId: string, file: File) => {
const body = new FormData();
body.append("file", file);
return requestUpload<{ icon_url: string }>(
`/agents/${encodeURIComponent(agentId)}/avatar`,
body,
);
},

deleteAvatar: (agentId: string) =>
request<void>(`/agents/${encodeURIComponent(agentId)}/avatar`, {
method: "DELETE",
}),
};
6 changes: 4 additions & 2 deletions dashboard/src/api/modules/octopThreads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface OctopThread {
model_ref?: string | null;
reasoning_mode?: "auto" | "enabled" | "disabled" | null;
reasoning_effort?: string | null;
artifacts?: string[];
}

export interface OctopThreadHistory {
Expand All @@ -37,6 +38,7 @@ export interface OctopThreadHistory {
turn_active?: boolean;
/** Pending tool approval for this thread (survives page reload). */
hitl_pending?: HitlPendingPayload | null;
artifacts?: string[];
}

export interface OctopThreadPatch {
Expand Down Expand Up @@ -156,9 +158,9 @@ export const octopThreadsApi = {
agentId: string,
threadId: string,
body: {
message_id: string;
message_id?: string;
content?: string;
user_turns_from_end?: number;
assistant_turns_from_end?: number;
},
) =>
request<{
Expand Down
Loading
Loading