* fix(infra): relocate wizard password file under OCTOP_HOME with legacy fallback
Derive the wizard password location from PathLayout root (OCTOP_HOME)
instead of hardcoding Path.home(). When OCTOP_HOME is relocated, the
password file is created and resolved from <OCTOP_HOME>/octop-login.txt.
To preserve complete backward compatibility for existing installations,
resolve_password_path, ensure_password, and read_password maintain a
fallback to legacy ~/octop-login.txt, migrating and synchronizing the
secret on boot when upgrading so no duplicate secrets are generated.
Closes#2171
* docs(changelog): record octop-login.txt OCTOP_HOME relocation fix (#2171)
* fix(backend): decode grep and shell output off the host code page
Chinese Windows decodes ripgrep and execute pipes as cp936, which crashes grep and silently drops UTF-8 command output. Patch both at startup so an Octop release fixes#1959 without a new harness build.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(backend): expect the locale code page to win over GBK text
GitHub Windows is cp1252, which accepts the GBK bytes of 中文测试, so those tests were asserting a Chinese round-trip the decoder must not produce. Keep a GBK-only byte that cp1252 rejects.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(bridge): keep team member streams alive through refresh
Cloud rooms dropped member tokens after the host turn ended, and a refresh mid-answer had nothing in history to restore. Hold the room subscriber and replay the in-progress member text when a client subscribes again.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: note cloud team stream refresh in the changelog
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(bridge): keep local login when peer credentials are rejected
Peer auth failure used HTTP 401, so probing cloud collab cleared the local session and showed Unauthorized.
Fixes#2166
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(dashboard): split chat nav into experts, teams, and cloud
Empty team and cloud lists can jump to create the first one, the host wrap-up lines up with the host avatar, and the cloud collab page explains how to connect.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(dashboard): add knowledge-base folder tree and unify list-panel rail toggles
- Knowledge bases: add a resizable folder navigation tree beside the document
list; move the breadcrumb into the docs toolbar and surface base id plus
creator on list rows.
- Knowledge bases / skill packages: move the collapse control onto the split
divider as a hover pill and drop the separate detail header.
- RailEdgeControl: pill button with center/before/after placement so two rails
can share one divider.
---------
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
int((speed - 1.0) * 100) truncates toward zero, so the float error of the
multiplication is kept: speed 1.4 became "+39%" and 0.8 became "-19%".
Round to the nearest percent so the slider value maps to the rate it says.
Scheduled jobs can run once and then stop, personalization tabs can be granted separately, and rotated logs use a date-before-extension name.
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(connectors): add DingTalk CLI (dws) connector with device login
Closes#930.
- New gateway connector `dingtalk-cli`: AppKey/AppSecret, per-instance
DWS_CONFIG_DIR, one `dws` tool taking argv; interactive/self-modifying
subcommands (auth, upgrade, skill, connect, completion) are rejected.
- Reuse the Agent Mail device-authorization flow for dws
(`auth login --device --no-browser`, `auth status --format json`,
`auth logout`), parameterized per connector kind.
- Dashboard: authorize DingTalk instances from the connector drawer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(changelog): note the DingTalk CLI connector (#930)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(connectors): make DingTalk CLI app credentials optional
dws device login uses DingTalk's official app when no client pair is
configured, so AppKey/AppSecret are only needed for a custom app. Accept
both-or-neither, and never inherit a host-wide DWS_CLIENT_ID pair.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(connectors): pass the dws command line as a string tool argument
The harness builds LLM-facing args models with mcp_args_model, which maps
non-scalar JSON types to str, so the `args` array schema reached the model
as a string and every real call failed adapter validation. Declare `args` as
the command line after `dws` and split it with shlex (no shell); a JSON array
string or list is still accepted. Found in an end-to-end run against a real
DingTalk org; regression test goes through mcp_args_model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(connectors): revoke DingTalk CLI login on delete and cap tool output
Deleting a dingtalk-cli instance only removed its local CLI directory: a
pending `dws auth login` kept running and the DingTalk grant stayed valid.
Route it through the same disconnect path as Agent Mail (cancel pending
login, `dws auth logout`, then delete). Also cap dws output at 32k chars,
like the Obsidian adapter, so chat-history dumps can't flood the context.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(connectors): close dws subcommand bypass and stdin inheritance
Self-review findings: `--profile x auth login` passed the denied-subcommand
check because only argv[0] was inspected, so require the product first
(only --help/-h/--version may stand alone). dws also inherited the server's
stdin, so a confirmation prompt would hang until the timeout; pass empty
stdin so it fails fast. Verified against a real dws: reads and --help work,
the smuggled form is rejected.
Pre-commit bypassed (SKIP_PRECOMMIT=1): the hook's affected-test run failed
only on tests/unit/agents/test_laya_runtime.py x3, which need a Python
3.10-3.13 on PATH (compatible_python() returns None under this machine's
3.14 venv) and are unrelated to this change. Lint/format pass; the DingTalk
and connector tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(dashboard): add DingTalk logo for the DingTalk CLI connector card
Like the other connectors, ship a 40x40 RGBA brand icon (DingTalk's
official site icon, downscaled) and register it in CONNECTOR_LOGOS so the
card no longer falls back to the generic icon.
Pre-commit bypassed: the hook's affected-test run failed only on
tests/unit/agents/test_laya_runtime.py x3 (need Python 3.10-3.13 on PATH;
this machine's venv is 3.14). npx tsc -b and the Connectors vitest suite
(18) pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: jubaoliang <jubaoliang@gmail.com>
The store compares only major.minor.patch, so 1.0.2b6 and 1.0.2b7 both
look like 1.0.2. Pack the pre-release into the patch written to the FPK
manifest while leaving the Python version and image tag unchanged.
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The slim step deleted playwright/driver, so the console browser install
failed before Chromium could download. The driver is the bundled Node
runtime, not the browser itself.
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Packages installed from the dashboard live in the image venv and disappear
when the container is recreated, while settings and indexes stay on the data
volume. Say so at startup, and bake the same extras into the manual FnOS
source image.
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Added a new function to resolve local model snapshots for ONNX models, allowing complete offline loading.
- Updated the embedding model builder to utilize local snapshots when available, enhancing performance and reducing external dependencies.
- Added a unit test to verify the correct usage of local snapshots in the embedding process.
Co-authored-by: jubaoliang <jubaoliang@gmail.com>
Agent grep with path=None walks from backend root `/`. Without ripgrep,
deepagents' readline() on /proc/pagemap can grow RSS until the kernel
OOM-kills Octop. Install rg in the images and replace the fallback at
process start with a walk that skips virtual filesystems and bounds each
read (#2101).
* feat(dashboard): pin chat experts and note missing remote plugins
Let the chat sidebar pin experts and search by expert or conversation title.
When a cloud expert calls a plugin that is not installed locally, show the
returned text and a short note instead of a raw field dump.
* fix(bridge): clear Generating when a cloud expert turn ends
Peers still stop relaying at harness done, so the dashboard never sees
turn.ended after mid-turn steering. Emit that frame locally when the
peer turn ends without one.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(chat): keep user message actions visible
Copy and edit under a user bubble were hover-only, unlike the assistant
message actions.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(experts): add Chinese-first business process expert
* docs(experts): consolidate business process attribution
* fix(experts): use supported business process icons
* fix(experts): complete business operations template integration
---------
Co-authored-by: jubaoliang <jubaoliang@gmail.com>
* feat(skills): export selected skills and skill packages as ZIP
Workspace skills can be checked and downloaded together, and a skill package
exports in the same top-level folder layout the ZIP importer already accepts. (#260)
* feat(skills): let skill packages disable ZIP download
Creators and admins can turn off package export. Other users can still view
and mount the package, but cannot download it.
* feat(skills): default package download from the copy policy
* test(db): expect schema v21 after package download migration
---------
Co-authored-by: 薄生 <67680641+Bosheng0422@users.noreply.github.com>
* feat: bridge plugin-contributed channel kinds (infra + API)
Octop-harness plugins can now contribute channel kinds (kind: channel)
that become first-class channels in the gateway and dashboard.
- infra/gateway/plugin_channels.py: apply_plugin_channels() replays
plugin channel registrations into octop_gateway.register_channel_kind
(idempotent, first-wins, per-plugin failure isolation);
plugin_channel_kinds() summarizes them for the dashboard catalogue.
- server.py: apply plugin channel kinds right after load_installed()
so kinds exist before gateway boot rebuilds DB channels.
- api/routers/channels.py: kind validation now accepts plugin kinds
(case-normalized) in create/patch/probe bodies, tolerant of harness
releases without all_channels(); new GET /channels/plugin-kinds
endpoint exposing the plugin channel catalogue.
Tests: 8 cases in tests/unit/test_plugin_channels.py (7 run on released
harness; the fixture end-to-end test auto-skips until the updated
harness is installed).
* feat(dashboard): merge plugin-contributed channel kinds into the catalogue
Fetch GET /channels/plugin-kinds once per mount and register each
returned kind into the channel catalogue at runtime (keys, labels,
icons, colors, per-kind config schema). Builtin kinds stay compile-time
constants; registration is idempotent and builtin kinds always win.
Kinds registered without a schema fall back to the drawer's existing
raw-JSON editing path, so any kind:channel plugin is configurable
without further frontend work. Fetch failure keeps a builtin-only
catalogue (older backends without the endpoint).
* docs: add plugin channel support entry to CHANGELOG
* fix(channels): expose registration errors and reload only changed kinds
* fix(plugins): support released harness and gateway in CI
* fix(channels): isolate persistent registration failures by kind
* fix: retry failed plugin channel removals without blocking refresh
---------
Co-authored-by: Chovy <chovy@local>
Co-authored-by: ideshun <28327099+ideshun@users.noreply.github.com>
Co-authored-by: 薄生 <67680641+Bosheng0422@users.noreply.github.com>
* feat: add a decision model for skills, tools, and teams
Let a small model shortlist what the chat model sees, including a local Laya install, and show the result on the turn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: stop Laya through the POSIX-safe kill helper
Windows mypy does not see os.killpg, so process-group shutdown goes through the shared helper.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: compare the Laya install command without a POSIX path
Windows quotes the interpreter, so the displayed command is checked against the same argv list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: show Laya readiness and wait for local decisions
Pin the local-CPU timeout fix and distinguish a ready Laya service from the service currently enabled for decisions.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: save each decision service and pick the default on the card
Clicking a card selects the default. Endpoint and key stay in the drawer
so Jev, Laya, and custom can be kept at once.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(knowledge): tell the user a restart is what the install needs
When the local embedding packages land in pip's user site, CPython only puts that
directory on sys.path if it existed at interpreter startup, so a long-running
server can never import them. ensure_local_embedding_deps() already raises with
'Restart the server and try again', but _map_knowledge_error() matched the text on
'embedding'/'component' and replaced it with the generic prerequisites error, so
the user retried a download that could not succeed. Map that case to its own code
and message.
* test(i18n): register the new code in the dashboard locales too
tests/unit/i18n/test_errors.py asserts the dashboard apiErrors keys equal the
backend ErrorCode set, so the new restart-required code has to be listed there
as well.
* fix(chat): show peer expert composer defaults and theme the beta badge
Cloud collab shadow experts dropped the peer's model, connectors, knowledge
bases, and conversation mode, so the local composer could not show them.
The sidebar Beta chip also stayed a fixed red instead of the theme color.
* fix: name experts in usage stats and title cron sessions
Admin usage grouped other users' experts by id because the page only knew
the signed-in account's agents. Scheduled runs also left the thread untitled
until the next user message when the push failed or never ran.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(weixin): keep a durable send token and repair old cron columns
Rename legacy cron_jobs.trigger to schedule_spec on startup. Store the WeChat context token on the session so cron can still deliver after a restart, and drop tool preambles on team WeChat streams.
* refactor(weixin): keep the send-token rules in one place
The session table no longer imports the gateway package. Token merge and team-stream preamble filtering behave the same.
* chore: require octop-gateway 1.0.2
The WeChat reply fixes and the QQ single-bubble replies ship in this gateway release.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Parked steers survive a follow-up approval, session bypass only auto-approves tools that allow it, and a withdrawn steer returns to the composer after the server confirms it is still queued. Stream errors stay in their own bubbles and follow the selected theme color.
- Updated README files to include instructions for using a domestic mirror when the official GHCR is unreachable.
- Introduced a new `docker-compose.cn.yml` file for pulling images from the Nanjing University GHCR source without local builds.
- Enhanced documentation to clarify the usage of the new Docker configuration for users in China.
`octop models active` prints " / " when no global default model is set:
the provider/model separator with nothing on either side and no hint.
`octop agent list` leaves the model cell empty for every agent without
its own model, which reads as "this agent has no model" and hides that
an agent with its own model overrides the global default.
When no default is set, `models active` now says so and shows the
command that sets one. `agent list` fills empty model cells with
"(global: provider/model)", or "(not set)" when there is no global
default either; agents with their own model are unchanged. Both strings
come from the zh/en bundles. JSON output (`--json`) is unchanged.
Refs #1930, #1931
Every download in the dashboard fetches the file with the JWT, wraps it
in a `blob:` URL and clicks an `<a download>`. WeChat and similar chat-app
WebViews (WeCom, QQ, DingTalk, Feishu, Weibo, Alipay) refuse to save
`blob:` downloads and only say "可在浏览器打开此网页来下载文件", while the
system browser they point to has no Octop session for the API URL.
In those WebViews `saveBlobAsFile` now hands the bytes it already
fetched to `POST /api/downloads/link`, which keeps them in a temp file
behind a random token for 10 minutes, and shows the link with a copy
button. `GET /api/downloads/link/{token}` serves the file as an
attachment without login; the token is the only credential. Uploads are
capped at `max_upload_mb`, links of disabled users stop working, and
expired files are removed on the next create/get. Normal browsers and
the desktop shell are unchanged.
Let users redirect an in-flight agent turn without stopping it.
Backend (`src/octop/infra/gateway/steer/` + WS channel + chat WS router):
- Per-thread steer queue with server-authoritative admission: idle input
starts a normal turn; busy input queues against the active turn with
optimistic binding (`expected_turn_id`), depth/byte caps, idempotent
retries, and structured reject reasons; HITL-paused turns reject new
steers.
- The queue drains only at provider-safe boundaries: after a completed
tool batch, the first model output preempts the stream (clean
checkpoint tail) and the queued steers continue the same logical turn
as a real persisted user message; a pure-text answer is never
interrupted and drains at the natural turn end.
- Lifecycle events on the thread socket: turn.started/resumed/ended,
steer.queued/drained/discarded/removed/rejected and queue snapshots on
subscribe; Stop and failures discard undrained items; steers parked
across an HITL approval pause drain after the approval resumes
(bounded by a 30-minute park TTL).
- Peer bridge relays the new user_turn fields so cross-instance turns
keep steer semantics.
Dashboard:
- While a turn streams, Enter parks the draft as a numbered chip above
the composer; the ↵ button on the chip commits it (immediate user row
in the transcript, settled by server frames), ✎ reclaims the full text
into the composer, ✕ removes it.
- The chip panel holds only uncommitted drafts; committed steers are
represented by their transcript row and settle to the canonical
server bubble at drain.
- Intermediate harness completion frames no longer close the live
socket or clear the active turn while a steer turn is in flight.
Docs: `docs/api.md` gains a Mid-turn steering section (frames, delivery
fields, drain-boundary rules); CHANGELOG entry under Unreleased.
Tests: unit coverage for admission/drain/discard/park semantics and the
WS channel boundary behavior; integration coverage for rebind and
multi-subscriber streaming with the new protocol frames.
Co-authored-by: jubaoliang <jubaoliang@gmail.com>
Distinct logins to the same account were collapsed onto one socket, and remote expert uploads never matched the tunnel allowlist. Inbound names stay local, and larger files split across frames once both sides negotiate it.