mirror of
https://github.com/volcengine/OpenViking.git
synced 2026-10-01 09:48:03 +08:00
python-sdk@0.1.9
77
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a809a0aaef |
feat(studio): show error details in request logs (#4172)
* feat(studio): show error details in request logs * fix(observability): harden audit error details |
||
|
|
056f875e90 |
feat: restore user-scoped memory extraction policies (#4126)
* feat: add user-scoped memory extraction policies * refactor: minimize user memory policy session changes * fix: resolve user memory policy consistently * refactor: resolve user memory policy once per commit * refactor: simplify memory policy provider typing * fix: apply server default user memory policy * fix: allow resetting user memory policy |
||
|
|
dc39985ad1 |
refactor: remove resource relation edges (#3956)
* refactor: remove resource relation edges * fix: remove stale relation references --------- Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com> |
||
|
|
eb5aaf78e9 | feat(mcp): consolidate recall into context search (#4075) | ||
|
|
d08d9b99e6 |
Feat/shared temp upload decouple (#4054)
* refactor(server): decouple shared temp uploads Remove shared-upload consumption locks and state transitions so uploads remain reusable within their account for 24 hours. Store them under the fixed internal viking://upload root, clean expired account-local directories on new shared uploads, and simplify metadata and deletion cleanup. Co-authored-by: TRAE CLI <noreply@bytedance.com> * feat(server): configure shared temp upload ttl Co-authored-by: TRAE CLI <noreply@bytedance.com> * refactor(server): share temp upload ttl Co-authored-by: TRAE CLI <noreply@bytedance.com> * refactor(server): rename temp upload ttl Co-authored-by: TRAE CLI <noreply@bytedance.com> * refactor(server): flatten shared temp uploads Co-authored-by: TRAE CLI <noreply@bytedance.com> * refactor(server): simplify shared upload metadata name Co-authored-by: TRAE CLI <noreply@bytedance.com> * refactor(server): remove legacy shared upload formats Co-authored-by: TRAE CLI <noreply@bytedance.com> * feat(server): allow disabling temp upload cleanup Co-authored-by: TRAE CLI <noreply@bytedance.com> Co-authored-by: TRAE CLI <traecli@bytedance.com> * refactor(server): timestamp shared temp upload directories Store shared uploads under timestamp-prefixed directories and use directory names for TTL cleanup instead of storage modification times.\n\nCo-authored-by: TRAE CLI <noreply@bytedance.com> Co-authored-by: TRAE CLI <traecli@bytedance.com> * test(server): prune shared upload tests --------- Co-authored-by: TRAE CLI <noreply@bytedance.com> Co-authored-by: TRAE CLI <traecli@bytedance.com> Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com> |
||
|
|
4920297ccc |
feat(mcp): add write/edit/tree tools for viking:// as agent working directory (#3936)
* fix(storage): keep non-memory appends free of memory trailers
ContentWriteCoordinator._write_in_place routed every append through
MemoryFileUtils, which strips the existing trailing newline and appends
a reserved MEMORY_FIELDS metadata trailer, even for resource/skill files
where MEMORY_FIELDS is not a reserved format (see content_visibility).
Append to non-memory files now concatenates raw content instead, matching
POSIX append semantics and the documented visibility rules.
* feat(mcp): add write tool with exact-string edit support
Agents could not use viking:// as a working directory through MCP: no
tool could create or update file content. Add a write tool covering full
writes (mode=replace as create-or-overwrite, append, strict create) and
targeted edits (a list of {old_string, new_string, replace_all}
exact-string replacements applied in order, all-or-nothing), following
the Write/Edit conventions of common agent harnesses.
Edits read via read_visible and write back through the content-write
coordinator, so memory metadata trailers are preserved and semantic /
vector re-indexing triggers as with any other write. Parent directories
are created automatically by the storage layer. Descriptions spell out
writable scopes (resources, user memories/resources, agent) and the
wait=true knob for read-after-write search consistency.
Also update the stale tool-count comment in app.py and the MCP tool
tables in the en/zh guides (13 -> 14 tools).
* feat(mcp): add tree tool, split targeted edits into edit tool
tree renders the recursive directory tree under a viking:// URI,
indented by depth with file sizes, for whole-layout orientation;
level_limit/node_limit bound the output and include_abstract adds
per-file summaries. Missing directories report "(nothing under ...)"
instead of an error, matching the read tool's convention.
edit(uri, old_string, new_string, replace_all) takes over the targeted
exact-string replacement that previously lived in write's edits array,
matching the classic Edit tool signature harnesses already train on.
write now only does full-content writes (content + mode), removing the
mutually-exclusive content/edits schema ambiguity. Edits still read via
read_visible and write back through the content-write coordinator, so
memory metadata trailers are preserved and re-indexing triggers as with
any other write.
* test(plugin): update canonical MCP tool list for tree/write/edit
The marketplace test pins the server-registered MCP tool list; add the
new tree, write, and edit tools to fix plugin-tests CI.
* feat(storage): support plain files at the user scope root
Agents treating viking:// as a working directory naturally drop files
like viking://user/zeus-persona.md at the user root, but the write
coordinator only accepted the memories/ and resources/ subtrees.
Two changes make that work:
- Namespace shorthand: a dotted first segment under viking://user/ is a
file name, not a user id (canonical user ids are dot-free by
convention), so viking://user/zeus-persona.md now canonicalizes to
viking://user/<current-user>/zeus-persona.md, matching how the
reserved memories/resources/skills segments already shorthand.
Dot-free segments still address an explicit user, and an exact match
with the current user id still wins.
- Coordinator: plain files directly under the user root (or in
non-managed subdirectories) anchor their semantic refresh at the
parent directory. The managed subtrees skills/, peers/, privacy/ and
sessions/ remain read-only with an actionable error message.
* fix(namespace): narrow user-root shorthand to text-file extensions
Review on #3936 (codex /review-pr) flagged that treating any dotted
segment as a user-root file shorthand would silently re-route canonical
URIs for valid dotted user ids (e.g. alice.smith) into the current
user space. Shorthand now triggers only when the first segment ends
in a common text-file extension; dotted or email-style user ids keep
resolving as canonical user ids. Adds regression tests pinning both
behaviors.
* fix(mcp): resolve user URIs against current user
* test(mcp): pin plain-file writes directly at the user root
The user-root shorthand exists so an agent can drop viking://user/persona.md
into its workspace, but every new test went through an intermediate directory
(viking://user/project/zeus-persona.md), leaving the no-directory shape — the
one that anchors the write coordinator's refresh at the user root itself —
uncovered. Add the missing case.
Also correct the write tool docstring: the create-extension allowlist applies
to any newly created file, including one created by mode="replace" falling
back to create, not only to an explicit mode="create".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
31e01c58a2 |
feat(admin): 清理已删除用户数据 (#3924)
* feat(admin): 清理已删除用户数据 删除用户时立即撤销身份,并通过持久队列完成用户数据清理。 * fix(admin): 删除用户时清理任务记录 * fix(admin): 避免过早判定用户任务取消失败 |
||
|
|
9097fef478 |
feat(server): refresh read-replica API key index via store watcher (#3857)
Read replicas load the API key store once at startup and never rewrite it, so a user registered/rotated/removed on the writer stays invisible (new key -> "Invalid API Key"; removed key -> still accepted). Add an optional background watcher that polls the shared key store and reloads the in-memory index only when it actually changes: - APIKeyManager.reload(): strictly read-only refresh that rebuilds state and swaps it in atomically, never writing or migrating plaintext keys. - compute_store_signature(): cheap (path, size, modTime) signature over accounts.json + every users.json so the watcher skips unchanged polls. - ApiKeyAuthPlugin starts/stops the watcher behind api_key_watch_enabled (default off) with api_key_watch_interval_seconds; AuthPlugin.shutdown() is wired into app shutdown to cancel it cleanly. Add coverage for reload convergence, read-only/no-migrate guarantees, uninitialized-store tolerance, signature change detection, and watcher reload/skip/shutdown behavior. Co-authored-by: TRAE CLI <noreply@bytedance.com> |
||
|
|
2ced3f1539 |
feat(agent-evolution): track experience trajectory lineage (#3727)
* feat(agent-evolution): track experience trajectory lineage * fix(agent-evolution): return matched trajectory records * fix(agent-evolution): stabilize lineage pagination |
||
|
|
49b182045b |
refactor(parser): Refactor code summaries to fixed skeleton-first routing (#3568)
* Refactor code summary skeleton routing
* Simplify code skeleton routing configuration
* Render C tag skeletons as signatures
* Revert "Render C tag skeletons as signatures"
This reverts commit
|
||
|
|
47bbf7a66a |
feat(cli): add Openviking asset manifest mode to add-resource (#3358)
* feat: implement server-resolved OpenViking Assets manifests Add the openviking-assets/1 declaration flow with server-owned configuration parsing and native Rust CLI execution. - Resolve one flat Manifest against one Catalog through an authenticated server endpoint with strict schema and Git semantic validation. - Reject recursive includes and unsafe clone URLs; return a resolved plan without submitting resources or running server-side batches. - Keep local credential aliases, manifest state, dry-run, failure isolation, and per-asset create/sync orchestration in the CLI. - Generate normalized stable asset identities on the server and remove the CLI direct SHA-1 dependency. - Update flat examples and add server resolver/API plus Rust CLI coverage. * feat: implement server-resolved OpenViking Assets manifests * feat: implement server-resolved OpenViking Assets manifests * fix(pathlock): tolerate missing lock token after recursive delete * feat: implement server-resolved OpenViking Assets manifests * feat: implement server-resolved OpenViking Assets manifests |
||
|
|
0ec2bb0ec5 |
feat: live-reload Agent Evolution and add file usage sink (#3573)
* feat(agent-evolution): reload global switch at commit time * feat(agent-evolution): expose configured account in status * test(agent-evolution): cover account in status response * fix(agent-evolution): align live config reload semantics * fix(agent-evolution): tolerate non-object live config * fix(usage-reporter): use snake case count fields * feat(usage-reporter): add file log sink * fix * fix: address live reload and usage sink review findings * fix(usage-reporter): complete file sink compatibility * fix: make experience snapshot source unambiguous * docs(usage-reporter): align count record implementation plan * fix: address agent evolution review blockers * fix(usage-reporter): preserve Windows rollover deadline * fix(usage-reporter): encode file records as JSON envelopes * fix(usage-reporter): use snake case unique id |
||
|
|
c61471ddc4 |
fix(deploy): harden bot and server deployment configuration (#3547)
* fix(bot): only require VKE credentials when the TOS storage path needs them Sweep findings: C-14. Gate AK/SK validation on an actual TOS deployment and drop the unused cluster ID. (cherry picked from commit |
||
|
|
8d087c0e39 |
feat(server): 增加 Request ID 日志关联 (#3572)
为 HTTP 请求提供可校验、可回传且贯穿服务端日志的关联标识。 |
||
|
|
8391d3a758 |
feat: add global Agent Evolution switch and HTTP usage sink (#3223)
* feat: add per-user agent evolution settings * simplify Agent Evolution user settings * fix: preserve agent evolution client compatibility * feat(snapshot): add path diff API * feat(snapshot): expose path diff in clients and CLI * fix(agent-evolution): gate case memory production * fix(agent-evolution): preserve configuration compatibility * feat(usage): add built-in HTTP sink * fix(agent-evolution): address PR review findings * fix(usage): isolate HTTP outbox by destination * docs(usage): define CountRecord HTTP mapping * docs(usage): plan CountRecord HTTP implementation * feat(usage): emit CountRecord over HTTP * docs(agent-evolution): design global switch * docs(agent-evolution): plan global switch migration * feat(agent-evolution): make production switch global * fix(agent-evolution): preserve embedded defaults * test(agent-evolution): cover failed archive policy replay * docs(agent-evolution): clarify embedded compatibility * docs(agent-evolution): expose global switch in example config * refactor(agent-evolution): align global setting terminology * fix(agent-evolution): preserve session skill extraction * fix(usage-reporter): capitalize count record keys |
||
|
|
78a143eeba |
fix(server): attribute MCP traffic in observability (route + identity) (#3494)
* fix(server): attribute MCP traffic in observability (route + identity) MCP requests were audited as route=/__unmatched__, account_id=__unknown__ because (1) the /mcp app is registered as a plain Starlette Route so scope["route"] is never set, and (2) _IdentityASGIMiddleware resolved identity without calling update_root_span_identity. Register /mcp via a _ScopedRoute subclass that sets child_scope["route"] on match (mirroring APIRoute.matches) and stamp root-span identity after resolution. 404 fallbacks and middleware code are untouched. * test(server): cover MCP scope route resolution and root-span identity stamping Assert the /mcp route sets scope["route"] on match (and that unmatched paths still fall back without it), and that _IdentityASGIMiddleware stamps the resolved account/user onto the root span attributes. * fix(server): attribute MCP traffic in observability (route + identity) MCP requests were audited as route=/__unmatched__, account_id=__unknown__ because (1) the /mcp app is registered as a plain Starlette Route so scope["route"] is never set, and (2) _IdentityASGIMiddleware resolved identity without calling update_root_span_identity. Register /mcp via a _ScopedRoute subclass that sets child_scope["route"] on match (mirroring APIRoute.matches) and stamp root-span identity after resolution. 404 fallbacks and middleware code are untouched. |
||
|
|
ccc271ff27 |
feat: add extensible usage reporting (#3222)
* feat: add extensible usage reporting * fix: harden usage reporter lifecycle * fix: scope experience usage events correctly * refactor: generalize usage event schema * docs: design Codex experience memory tools * docs: plan Codex experience memory tools * feat: add Codex experience memory tools * docs: remove temporary Codex implementation plans * fix: capture Codex MCP tool parts * fix: harden experience usage reporting * fix: reload credentials for local MCP tools * fix: preserve MCP tool-level errors * fix: bound synchronous sink shutdown * fix: enforce sink shutdown timeout * fix: enforce experience tool contracts * fix(usage-reporter): keep tool schemas and replay ids stable * fix(usage-reporter): reject unidentifiable tool events |
||
|
|
d47f2106ee |
refactor: remove unused and deprecated APIs (#3272)
Delete dead compatibility paths and test-only helpers so unsupported APIs do not remain as accidental contracts. |
||
|
|
c99e65472b |
fix(server): default OAuth client scope for scope-less DCR (ChatGPT invalid_scope) (#3210)
#2921 persisted the DCR scope when the registrar sends one and started advertising scopes_supported=["mcp"] in the PRM document. ChatGPT's DCR omits the scope field, so its client registers scope-less, then requests the advertised scope=mcp at /authorize and gets bounced back with error=invalid_scope before any consent page renders. - app.py: pass default_scopes=["mcp"] to ClientRegistrationOptions so scope-less registrations get the default grant; valid_scopes stays unset so clients that register their own scope strings are not rejected at DCR time - provider.py: single-source the scope as MCP_SCOPE; get_client() falls back to it for NULL-scope rows, repairing already-registered clients without migration or re-registration - router.py: reuse MCP_SCOPE in the PRM scopes_supported - tests: provider fallback unit tests + end-to-end scope-less DCR and legacy NULL-scope client authorize regressions |
||
|
|
d7b96d7715 |
feat(server): add user add target defaults (#2888)
* feat: add user add target defaults Allow deployments and per-user settings to provide default add targets while keeping explicit request targets authoritative. * test: remove low-value CLI config parsing test * refactor: unify user config option naming |
||
|
|
aa53e7aede |
feat: 实现 commit、restore、show 文件系统多版本管理功能 (#2756)
* feat: 实现 commit、restore、show 文件系统多版本管理功能 fix: 修复commit时删除文件 fix: commit 的 fast path 1 添加 Racy-clean 机制 fix: 将 sdk 中的 git 命令改为 snapshot 命令,同步修改单测 fix: 多版本管理的文件存储目录改为 .ovgit feat: snapshot cli 渲染 fix: 修复 restore 时将删除的文件回滚时,目录不存在的问题 feat: restore 命令的 project_dir 参数改为可选,不传时默认全目录回滚 feat: 更新文档 fix: 删除暂未使用的配置参数 feat: 新增示例脚本 fix: 修复示例代码 fix: 修复 restore 返回的 task id 任务完成状态 feat: 在 restore 修改文件系统时加锁 fix: fix openviking_sdk * feat: 将git多版本管理功能改为默认打开,并复用agfs的配置参数作为默认值 * fix: restore 命令改为先完成 ref 一致性协议再写回 VFS;object store 并发改为使用唯一 temp path * fix: 在 git 配置检验层去除未实现的cas_mode = "redis_lock"模式 * fix: 在 Rust GitService 边界统一校验 account * fix: 当前commit不支持通过 path 传入目录,增加报错信息 * fix: 将git文件默认存储路径统一为 .ovgit * fix: restore 时写入 VFS 失败时返回详细的报错,并继续触发 reindex * fix: 校验 commit、restore、show 的路径 * feat: 实现 commit 时指定目录 --------- Co-authored-by: zhanghaoyu.la <zhanghaoyu.la@bytedance.com> |
||
|
|
87329714dd |
feat(grep): integrate VikingDB bm25 keyword search for grep engine (#2144)
* feat(grep): integrate VikingDB bm25 keyword search for grep engine * fix(grep): address CI review feedback: max-size eviction to _count_cache, use Literal, Split regex alternation into individual keywords for bm25 (max 10) * fix(schema): use dynamic __version__ for schema_version and handle dev suffixes in version comparison * fix(schema): upsert data to vikingdb lack of content * chore: add benchmark for retrieval * fix(grep): vikingdb return 200 and no results means no matching content, not necessary to fallback to local fs * fix(benchmark): sub uri args; add report * refactor: code format by ruff * optimize: move grep config (engine and switch_to_remote_threshold) to ov.conf * optimize: auto adapt remote_return_limit by agg API; rm unnecessary params in keywords search * fix: adjust benchmark scripts * fix(grep): store full content for BM25; use PathScope depth; reduce redundant API calls * refactor: new benchmark * fix: step1 add resource by real code data * feat(benchmark): split grep benchmark into effectiveness/performance suites with async reindex * optimize (benchmark): adjust keywords and ground truth for testing * fix: truncate 64KB for content field * optimize: effectiveness add resource plainly * optimize: change param use of SearchByKeywords from "keywords" to "query" * optimize(benchmark): refactor effectiveness scripts * optimize: ensure raw data for content field * optimize: fulltext analyzer's stop-words only use symbols * fix: adapt to new ov cli for benchmark * optimize: reuse file content to avoid re-read AGFS file * optimize: tune grep vikingdb defaults and refresh bm25 benchmark scripts * optimize: benchmark client timeout * update README * fix: rm unused param * fix: default values in docs * optimize: increase truncate byte size to 1MB for content field for VikingDB * fix(logger): harden queued stream logging (#2786) * fix(logger): replace StreamHandler with QueueHandler+QueueListener to prevent thread deadlock When log.output='stdout' (default) and the server is managed by systemd, concurrent log writes can deadlock because logging.StreamHandler holds a thread lock across stream.flush() which blocks on systemd-piped file I/O. During session.commit() phase 2, multiple async coroutines (memory extraction, summarization) concurrently call logger.info()/warning() with large payloads. The first thread's flush() blocks on the pipe, while all subsequent threads block on handler.acquire() forever. This permanently silences the server log and prevents _write_done_file() from executing, leaving phase 2 hanging without .done. Fix: use QueueHandler + QueueListener from stdlib logging.handlers (Python 3.2+). QueueHandler.emit() does queue.put(record) with no lock or I/O, returning immediately. QueueListener has a dedicated single thread as the sole consumer touching the real StreamHandler, making lock contention impossible. Changes in _create_log_handler(): stdout/stderr branches now create a shared QueueListener with unbounded queue, returning QueueHandler instances to callers. _build_standard_handler() delegates formatter and filter setup to the real handler in the listener thread. Closes: #2752 * fix(logger): harden queued stream logging --------- Co-authored-by: njuboy11 <njuboy11@users.noreply.github.com> --------- Co-authored-by: Qin Haojie <qinhaojie.exe@bytedance.com> Co-authored-by: njuboy11 <njuboy11@users.noreply.github.com> |
||
|
|
ab656e240d |
refactor(auth): introduce plugin-based authentication architecture (#2709)
* chore: clear unused files * fix(tests): fix unit test * refactor(auth): introduce plugin-based authentication architecture Replace the monolithic `openviking/server/auth.py` with an extensible plugin-based auth system. This refactor extracts the three built-in modes (`dev`, `api_key`, `trusted`) into separate `AuthPlugin` implementations, adds a registry for third-party plugins, and preserves all existing behavior while enabling custom authentication backends (e.g. LDAP, OIDC, mTLS). Key changes: - **New public API**: `AuthPlugin` (ABC) and `register_auth_plugin` decorator. - **New registry**: `AuthPluginRegistry` supports runtime registration. - **Built-in plugins**: `DevAuthPlugin`, `ApiKeyAuthPlugin`, `TrustedAuthPlugin`. - **Config change**: `auth_mode` widened from `Literal` to `str` for custom modes. - **Validation delegated**: `validate_server_config()` now delegates to the active plugin's `validate_config()`, preserving existing validation semantics. - **Router compatibility**: All existing `require_*` decorators and `resolve_identity` / `get_request_context` dependencies remain unchanged. Routers import the same symbols from `openviking.server.auth`. - **Tests**: `conftest.py` manually wires the DevAuthPlugin in ASGI tests (lifespan not triggered). `test_auth.py` expanded with plugin registration and validation tests. - **Docs**: `04-authentication.md` (en/zh) updated with plugin registration examples. Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com> * fix(tests): fix trusted mode test * fix(tests): fix unit test --------- Co-authored-by: claude-sonnet-4-6 <noreply@anthropic.com> |
||
|
|
ecced9930a |
feat(code-tools): add code navigation endpoints (#2671)
Add HTTP APIs for code outline, search, and expansion backed by the existing AST tooling. Expose the same capabilities through the opencode plugin and cover the new routes, parser behavior, and plugin wiring with tests. |
||
|
|
8a3bfb68ff |
chore(oauth): remove dead push-OTP, repurpose footer to cross-device verify (#2538)
The push-OTP feature (mint an OTP in Studio to hand to an MCP client) was never wired to a consumer: consume_otp had zero production callers and no endpoint or grant ever redeemed an OTP. The 'full happy path' test actually exercised the display_code flow, not OTP. So the sidebar footer's 'OAuth setup' entry minted a code with nowhere to use it — dead, confusing UX. Remove it end-to-end and repurpose the footer slot into an entry for the cross-device verify page (enter the 6-char display_code), which previously had no discoverable entry point in Studio. Frontend: - delete oauth-setup-dialog.tsx + /oauth/setup route (+ routeTree, i18n) - extract CrossDeviceVerifyForm from verify.tsx; add CrossDeviceVerifyDialog - footer 'OAuth verify' entry opens the verify dialog (desktop) / page (mobile) Backend: - drop issue_otp route + OTPRequest/OTPResponse, storage insert_otp/consume_otp, oauth_config.otp_ttl_seconds, and the OTP-specific tests - keep otp.py generate_otp (cross-device display_code) + hash_secret, the shared _atomic_consume_code, and the oauth_codes.kind column - convert the race/expiry/revoke/GC storage tests to auth-code rows Docs: update 11-oauth, 06-mcp-integration, and the design doc to reflect removal. |
||
|
|
78471b6485 |
feat(skill):Add skills cli and api, support add skills from git, support update skills (#2453)
* add skills cli and api * add api source * fix agent space * fix bug * fix bug * fix pr bug * fix pr bug * fix pr new bug |
||
|
|
7ee1481e1d |
feature(storage): support multi write storage (#2466)
* feature(storage): support multi write storage * refactor(storage): simplify multiwrite logic and consolidate test helpers * refactor(storage): extract multibackend and shape modules and tighten multi-write wrapper boundaries * refactor(storage): refactor write pipeline |
||
|
|
ff258768c2 |
feat(memory): 引入 User/Peer 记忆隔离模型 (#2236)
* feat(memory): introduce user and peer memory isolation Unify agent-scoped memory behavior into user-owned memory spaces, add peer_id compatibility for session and retrieval paths, and wire memory_policy through session commit flows. * feat(memory): align session identity around peer IDs * feat(search): pass peer id through retrieval * refactor(memory): remove agent identity from integrations * fix(memory): isolate peer identity from self extraction * fix(tau2): provision benchmark user configs * fix(auth): allow admin keys to access data APIs * fix(openclaw): enable peer memory policy for peer roles * fix(openclaw): resolve sender for peer recall * refactor(session): simplify memory extraction routing * refactor(ov-cli): reduce formatting-only diff * refactor(message): remove unused message helpers * refactor(retrieval): simplify peer target resolution * refactor(namespace): remove deprecated agent namespace policy * fix(agent): propagate peer id through integrations * fix(auth): align integration clients with api-key mode |
||
|
|
e9e6ce5e9a | feat(server): add request-scoped http profiling (#2125) | ||
|
|
4e9473406b |
feat(web-studio): PWA, mobile UI polish, OAuth-setup tab, request-logs touch-ups (#2178)
* feat(web-studio): PWA, mobile UI polish, OAuth-setup tab, request-logs touch-ups
PWA & install
- manifest 192/512 + maskable icons, service-worker registration in main.tsx,
apple-mobile-web-app meta + viewport-fit=cover in index.html.
- openviking/server/app.py: redirect "/" -> "/studio/" so the PWA install
start_url resolves correctly when accessed from the bare host.
OAuth setup as its own tab
- New /studio/oauth/setup route + OAuthSetupDialog; sidebar footer entry
routes to the dialog on >=md and to the dedicated page on phone (mobile
fullscreen dialogs are hard to dismiss). OTP form extracted from the
connection dialog into a reusable component.
Mobile UI fixes
- Home metric chips: grid minmax 92px -> 140px, removed truncate so chips
wrap instead of clipping.
- Context-commits tooltip: clamps into viewport on narrow screens.
- Context-commits heatmap: auto-scrolls to rightmost (latest) on items
change so phone view opens on the current week.
- Dialog: max-h-[calc(100dvh-2rem)] + overflow-y-auto for phone overflow.
- FS page (resources):
* full-height container uses calc(100svh-6rem) + symmetric -my-6 so the
outer ScrollArea no longer overflows by parent's py-6.
* inner overlay-style scrollbars on phone (matches iOS / Atlas).
* PWA standalone mode hides the outer page scrollbar on this route.
* Tighter row density on phone (text-xs / py-1) to match Atlas.
* FolderIcon size-5 -> size-4 so folder and file text columns align.
* Folder highlight only when no file is selected (clean single-select).
* Toolbar refresh button: size-icon-sm + pl-4 to align with the header
sidebar trigger on phone.
- Find-palette: phone-center + max-h-[calc(100svh-2rem)] (was top-anchored
+ 84vh). Search input min font-size 16px to dodge iOS auto-zoom; same
treatment in the retrieval search bar.
Request logs
- Time column: dot-separated YYYY.MM.DD HH:MM:SSam/pm (compact, no locale
variance).
- Total calls clamps display at "999+".
- Pagination row: justify-center on phone (sm:justify-between preserved).
i18n
- Retrieval placeholder: "Search context" / "输入检索内容".
- Find-palette placeholder: "Search" / "搜索".
- Context-commits title: drop "in the last year" (range chip still shows).
- find / search labels: drop the parentheses.
- Workspace sidebar header: "OpenViking Studio".
Build verified after each change. Phone view validated via Chromium
emulation; iOS PWA-mode scrollbar fix scoped behind
@media (display-mode: standalone).
* feat(web-studio): more mobile polish and viewer-tz bucket shift for commit heatmap
PR #2178 follow-ups based on phone PWA testing:
Context commits heatmap (Home)
- Re-bucket UTC (date, hour_bucket) 4h rows into viewer-local (date,
hour) before daily aggregation, so a UTC+8 viewer's "today" cell holds
the right local-day count instead of UTC's. Pure client-side relabel —
each bucket's count is preserved, just attributed to the correct local
date. Daily / "today_tokens" aggregates can't be rebucketed this way
and still need the backend tz work (separate follow-up).
- Auto-scroll: align viewport's right edge with the rightmost rendered
rect's right edge (not the SVG's hardcoded width=820 right edge), so
the ~50px of trailing SVG padding stays off-screen on phone.
- Wrap the scroll write in requestAnimationFrame so HeatMap has a tick
to lay out its SVG; otherwise scrollWidth equals clientWidth and the
scroll snaps to the left (oldest) edge.
FS / resources route polish
- File tree: clicking a folder row now toggles expand/collapse in
addition to opening the folder (chevron icon still works alone via
stopPropagation, no double-toggle).
- File tree: parent passes selectedFileUri only when the selected entry
is actually a file; opening a folder no longer leaves it competing
with the parent's directory highlight.
- File tree: session subtree (any URI under viking://session/) sorts
children by modTimestamp DESC instead of name, so the most recent
session shows first.
- FolderIcon size-5 -> size-4 to match File icon and chevron, so folder
and file rows share the same text x-offset at every depth.
- Toolbar: refresh Button now uses size="icon-sm" + pl-4 on the toolbar
so its icon center aligns vertically with the header's sidebar
trigger on phone.
- Breadcrumb nav: overflow-hidden -> overflow-x-auto + whitespace-nowrap
+ flex-1 so long paths scroll horizontally on phone instead of being
cut off invisibly.
formatModTime
- Parse time-only mod_time strings ("HH:MM:SS", "12:34am" etc) against
UTC today + Z marker — backend's format_simplified uses UTC's "today"
reference, not the viewer's local today, so the prior local-today
prefix mis-attributed days for non-UTC viewers.
- Date-only mod_time ("YYYY-MM-DD") renders as-is; we don't fake an
"00:00" time component that doesn't exist.
JSONL dialog
- Cards: drop ml-auto / mx-auto / mr-auto chat-bubble alignment;
switch w-fit max-w-[min(820px,88%)] to w-full min-w-0 max-w-full so
long expanded messages, tool-call JSON, and markdown tables can use
the full pane width and trigger their own internal overflow-auto
instead of pushing the article wider than the viewport.
- List container picks up min-w-0 so the flex column can shrink.
- Kind distinction now color-only (bg / border).
Request-logs pagination
- justify-center on phone for both the summary row and the pagination
control row; sm:justify-between / sm:justify-end preserved on tablet+.
|
||
|
|
d6a024efa5 |
feat(docker)!: drop legacy console (keep BFF + Caddy), ship web-studio in pip, fix favicons (#2160)
The OpenViking docker image still launched the legacy `openviking/console` standalone service on port 8020. Now that web-studio is bundled into the OV server itself at /studio (see #2156), that process is redundant and the port is just a confusing artefact. This change retires the old console (python package + 8020 + console-frontend favicons) but **keeps the in-compose Caddy as a stable single-ingress on port 1934**, just simplified to one upstream now that there's no 8020. The server-side BFF at `openviking/server/routers/console.py` (under `/api/v1/console/*`) is also kept — web-studio uses the same endpoints. **The OAuth authorize page (`openviking/server/oauth/router.py`) is deliberately untouched in this PR** — the console-link button and Quick authorize same-origin panel will be re-pointed at web-studio in a focused follow-up. BREAKING CHANGES: - Port 8020 is gone from the docker image and docker-compose.yml; Caddy at 1934 now forwards everything to 1933 (web-studio lives at /studio there). Anything bookmarked at `http://host:8020/...` must migrate to `http://host:1933/studio/`. - `python -m openviking.console.bootstrap` no longer exists; the python package `openviking.console` has been removed. Pip packaging: - web-studio dist is now shipped inside the wheel under `openviking/web_studio/dist/` (mirroring the old `openviking/console/static/` layout). The dockerfile copies `--from=web-studio-builder /web-studio/dist` into the source tree before `uv sync`, so the wheel produced by the default docker build always carries the SPA. Building the wheel without running `npm run build` first leaves the directory empty, which gracefully degrades /studio to a 404 without breaking server startup. - Favicon assets (`favicon.ico` / `favicon-32.png` / `apple-touch-icon.png`, ~11 KB total) are duplicated into `openviking/server/static/` and shipped via package-data so `/favicon.*` and `/mcp/favicon.*` routes are always registered, regardless of whether the web-studio dist is bundled. - `pyproject.toml` and `setup.py` `package-data` drop `console/static/**` and add `server/static/**` + `web_studio/dist/**`. - New favicons (the 16/32/180 set in both `openviking/server/static/` and `web-studio/public/`) are downscaled from the canonical `web-studio/public/openviking-icon.png`, so the small-icon family matches the SPA's high-res rel="icon" target — the studio tab icon now stays consistent whether the browser uses the HTML link tag or falls back to auto-fetching `/favicon.ico`. Server: - `openviking/server/app.py` now reads `/studio` from `Path(__file__).parent.parent / 'web_studio' / 'dist'` by default; `OPENVIKING_WEB_STUDIO_DIR` still wins for dev mode pointing at a repo-local build. Favicon routes are unconditionally registered and load from `openviking/server/static/`. - `openviking/observability/usage_audit/projection.py` drops the legacy `/console/*` skip prefix (the BFF prefix `/api/v1/console/*` remains). Docker: - `web-studio-builder` stage moved earlier (Stage 2) so its dist can flow into `py-builder` before `uv sync` runs. - Runtime stage no longer separately copies the dist or sets `OPENVIKING_WEB_STUDIO_DIR`; the in-package path is the default. - Entrypoint renamed `openviking-console-entrypoint.sh` -> `openviking-entrypoint.sh` and stripped of the `python -m openviking.console.bootstrap` launch. - `EXPOSE 1933 8020` -> `EXPOSE 1933`. - `docker-compose.yml` drops the openviking service's 8020 port mapping; the caddy service stays but no longer needs port 8020 exposed. - `Caddyfile` simplified to a single `:1934 { reverse_proxy openviking:1933 }` — the legacy `/console/*` route to :8020 is gone. Docs: - en/zh quickstart updated to drop the 8020 mapping and explain that the API server now also serves `/studio`. - Other guides (`12-public-access.md`, `11-oauth.md`, `05-observability.md`, `04-setup-for-agent.md`, `03-deployment.md`) are intentionally left for a focused follow-up PR alongside the OAuth quick-authorize reintroduction. Tests: - Deleted `tests/misc/test_console_{proxy,static_assets}.py` (covered the removed console package). `tests/observability/test_console_router.py` stays — it covers the BFF, which remains. |
||
|
|
f39b030926 |
feat: externalize oversized session tool results (#2058)
Squashed commits: - 28e175c8 feat: externalize oversized session tool results - 73d6461d feat: coalesce OpenClaw tool results by turn - 3bf4a0c2 fix: apply tool result config and filtered listing - a5bc0582 [bugfix] extractNewTurnMessages会错误过滤掉没有text block的assistant toolCall,用[toolCall: toolName]占位符确保消息传入 - 1d76827b style: format tool result compression changes - 495b5623 fix: preserve textless toolUse turns - 399f2dc3 feat: expose tool result access tools - 43b48dd7 fix: honor min preview chars for source reads - 6eaaf54f fix: split aggregated tool result messages - f8ad98c3 fix: hydrate tool outputs for extraction - f9031458 fix: improve tool descriptions for tool result access tools - ae3bcd33 fix: hydrate source-read tool outputs - 8976f8ce test: add tool result compression bench cases |
||
|
|
cb8e1e6842 |
feat(web-studio): bundle web-studio into docker, serve at /studio (#2156)
* feat(web-studio): bundle web-studio into docker, mount at /studio + favicon to public
- Dockerfile: add node:20 build stage for web-studio, `vite build --base=/studio/`
output to /app/web-studio/dist. New ENV OPENVIKING_WEB_STUDIO_DIR.
- server/app.py: serve /studio and /studio/{path:path} from dist; SPA deep-link
fallback to index.html. Favicon routes (/favicon.ico, /favicon.png,
/apple-touch-icon.png, /mcp/favicon.ico+png+touch) read from the same dist —
no more separate console/static dependency.
- web-studio/public: ship favicon.ico, favicon-32.png, apple-touch-icon.png
inside the SPA bundle so they end up in dist root.
Old console (8020) untouched in this branch.
* fix(web-studio): drop hardcoded 127.0.0.1:1933 fallback in ovClient default
ovClient was initialized with `baseUrl: ENV_BASE_URL || 'http://127.0.0.1:1933'`,
which short-circuited normalizeBaseUrl's window.location.origin fallback. As a
result, when web-studio was served by the OV server itself (e.g. bundled in the
OV docker image at /studio/), the SPA would try to call back to the user's local
127.0.0.1:1933 instead of the same origin it was loaded from — making the
bundled deployment unusable.
Removing the literal fallback lets normalizeBaseUrl pick window.location.origin
when ENV_BASE_URL is empty, which is the right default for every "served by OV"
shape (bundled image, port-forwarded docker, reverse-proxied custom domain).
Dev workflows running vite separately can opt in by setting VITE_OV_BASE_URL or
overriding the URL in the in-app Connection dialog (which persists to
localStorage).
---------
Co-authored-by: alice <alice@zouk-agent.local>
|
||
|
|
41a33ec16a |
feat(mcp): progressive single-entrypoint upload for local files (#1847)
* feat(mcp): progressive single-entrypoint upload for local files
Extends `add_resource` MCP tool to handle local-file paths via a server-orchestrated
two-step flow, eliminating the need for `ov` CLI in sandboxed agent environments
(Claude web, Manus) where local FS is unavailable and CLI install is blocked.
Behavior:
- Remote URL → unchanged.
- Local path → server mints a 6-char base62 token, returns prose Step 1 / Step 2
instructions pointing at /api/v1/resources/temp_upload_signed.
Agent uploads, then re-calls add_resource(temp_file_id=...).
- temp_file_id → resolved against per-tenant subdir, ingested via existing pipeline.
Token: in-memory dict, 10-min TTL, dict.pop doubles as replay protection.
Per-tenant temp-dir isolation ({root}/{aid}/{uid}/{tfid}); legacy CLI uploads
keep flat layout via dual-lookup in resolve_uploaded_temp_file_id.
Public base URL resolves env > config > listen-host fallback (12-factor: runtime
env trumps image-baked config; production deployments behind MCP proxy + nginx
must set OPENVIKING_PUBLIC_BASE_URL since the agent-facing URL is not derivable
from the server's request scope).
* feat(mcp): infer public base URL from request headers + emit fallback hint
Adds a third fallback layer between explicit operator config and listen-host
fallback: capture X-Forwarded-Host / X-Forwarded-Proto / Host headers in the
MCP identity middleware and use them when neither OPENVIKING_PUBLIC_BASE_URL
nor ServerConfig.public_base_url is set.
Resolution order is now: env > config > X-Forwarded-* > Host > listen-host.
The first two are explicit; the rest are inferred. When an inferred source is
used, the add_resource prose response appends a troubleshooting hint asking
the user to set OPENVIKING_PUBLIC_BASE_URL on the server if upload fails —
because inferred URLs can be wrong if the reverse-proxy chain doesn't forward
X-Forwarded headers, or if the server listens on 0.0.0.0.
Documents the variable in docker-compose.yml (commented-out env block) and
in the MCP integration guides (zh + en) — covers when it's required and the
full resolution chain.
* fix(mcp): address Copilot review on PR #1847
- Relax temp_file_id regex from `[a-zA-Z0-9]+` extension to any non-separator
chars, and dedupe to a single TEMP_FILE_ID_RE in local_input_guard. The old
pattern rejected `Path("report.my-file").suffix == ".my-file"` and similar
legitimate filenames, breaking the progressive upload flow.
- Hoist `_resolve_temp_or_path` import to module level in mcp_endpoint
(verified no circular import).
- Add `_is_safe_namespace_component` defense-in-depth at the signed-upload
route so a future code path that mints tokens from less-trusted input
still cannot escape the per-tenant directory.
- Broaden partial-file cleanup to any exception via try/finally + flag,
not just HTTPException — prevents OSError/IO failures from leaving
half-written files behind.
- Scope `_cleanup_temp_files` to the tenant subdir at the signed-upload
route to bound the rglob scan; the legacy `/temp_upload` route still
cleans the root level.
- Add round-trip test for unusual filename extensions (.my-file, .bak~, .中文).
* docs(mcp): reflect server-minted temp_file_id in progressive-upload flow
Post-rebase onto TempUploadStore, the agent no longer learns the temp_file_id
from the MCP prose — the server mints it at upload time and returns it in the
JSON response body. Update both en + zh docs accordingly. Also note that the
signed endpoint shares the same persistence layer as /temp_upload, so
local/shared modes (and multi-worker via shared) apply uniformly.
* fix(mcp): address Copilot review on rebased PR #1847
- Drop `upload_signed_max_bytes` config field. The signed endpoint now relies on
TempUploadStore's streaming `temp_upload.shared_max_size_bytes` check (single
source of truth, fires even when Content-Length is missing/chunked). Map
oversize from InvalidArgumentError back to 413.
- Normalize `X-Forwarded-Host` / `X-Forwarded-Proto` to the first comma-separated
value in `_resolve_public_base_url`, matching the OAuth issuer resolver. Fixes
malformed upload URLs under multi-hop proxy chains.
- Complete the `public_base_url` field comment to reflect all five fallback layers
in the resolver, not just env > field > listen.
- Add `watch_interval` / `to` parameters to the MCP tool tables in both en + zh
integration guides — they were merged in from main's Watch Management API
during the rebase but the table wasn't updated.
|
||
|
|
74414c9464 |
feat: Watch Management API — REST + ov CLI + MCP (RFC #2104) (#2110)
* feat(mcp): add watch_interval + to params to add_resource Net-new MCP capability — main branch's add_resource tool had no watch entrypoint. Watch_interval > 0 triggers periodic full re-ingest via existing WatchScheduler, requires explicit `to` URI to avoid collision in WatchManager._uri_to_task reverse index. Part of P3 of RFC #2104 (Watch Management API). * feat(server): add REST /watches management endpoints Implements P1+P2 of RFC #2104 (Watch Management API). New /api/v1/watches router exposing: - GET /watches (list, with active_only filter; supports ?to_uri= for single lookup) - GET /watches/{task_id} - PATCH /watches/{task_id} or ?to_uri= (partial update: watch_interval, is_active, reason, instruction — orthogonal pause via is_active) - DELETE /watches/{task_id} or ?to_uri= - POST /watches/{task_id}/trigger or /watches/trigger?to_uri= (immediate refresh, does not wait) Reuses WatchManager primitives directly — zero backend changes. Translates watch_manager.PermissionDeniedError (plain Exception) to the OpenVikingError-rooted variant so the global handler renders 403. Tests in tests/server/test_api_watches.py cover empty list, full lifecycle, by-uri lookup, active_only filter, dual-key behavior, missing key, 404, extra-fields rejection, trigger-by-uri, and partial PATCH. * feat(mcp): add list_watches and cancel_watch tools Implements P4 of RFC #2104. MCP gets the minimum closure: list + cancel. Pause/resume/trigger/set-interval are intentionally not exposed — those are power-user operations belonging on the ov CLI, not in the agent's system prompt. cancel_watch uses to_uri as the primary key (agents don't track UUIDs). list_watches returns a compact one-line-per-task format with URI, interval, status, and next-run timestamp. Tests cover empty/seeded list, cancel-by-URI, cancel-not-found, plus the add_resource watch_interval-without-to error hint added in the prior commit on this branch. * feat(cli): add ov watch subcommand group Implements P5 of RFC #2104. Full parity with the REST /watches surface: ov watch ls [--active-only] ov watch show <key> ov watch rm <key> ov watch pause <key> ov watch resume <key> ov watch set-interval <key> <minutes> ov watch trigger <key> <key> auto-detects by prefix: a "viking://" string routes to the by-uri HTTP endpoint, anything else is treated as a task_id and routes to the path endpoint. Saves users from remembering UUIDs. Adds patch() and post_with_query() helpers to BaseClient (PATCH was absent; post_with_query supports POST .../trigger?to_uri=...). Output flows through the existing OutputFormat (Table / JSON) helper unchanged. * fix: address Copilot review on PR #2110 REST router (`watches.py`): - _resolve_task: relax dual-key handling. When both {task_id} and ?to_uri= are supplied, accept if they refer to the same task (useful cross-key check), reject only on disagreement. Expose ?to_uri= on the by-id GET / PATCH / DELETE / trigger routes so this is reachable from HTTP. - _patch_impl: drop fragile "not found" substring matching. _resolve_task pre-checks existence; if WatchManager.update_task still raises ValueError (race window), map to 404 — matches the pre-check behavior. Add a docstring explaining why ConflictError and other OpenVikingError-rooted exceptions are intentionally allowed to bubble for the global handler. MCP (`mcp_endpoint.py`): - add_resource: reject negative watch_interval. Previously a negative value silently bypassed the `watch_interval > 0 requires to` check and was forwarded with undefined semantics. - cancel_watch: treat delete_task returning False as idempotent success. Race-removal between our lookup and delete is a benign concurrent cancel — the desired post-condition holds either way. Tests: - Rename test_dual_key_conflict_returns_400 → test_dual_key_matching_accepted (the matching case is now valid). Add test_dual_key_mismatch_returns_400 for the genuine conflict path. - Rename test_patch_to_uri_conflict_returns_409 → test_patch_rejects_unknown_field to match what it actually verifies. CLI (`commands/watch.rs`): - set_interval: local-validate `minutes > 0` before issuing the HTTP request so we fail fast with a clear message instead of a generic server 400. Addresses: comments 1, 2, 3, 4, 6, 7, 8 on PR #2110. * fix: address second round of Copilot review on PR #2110 REST router (`watches.py`): - _trigger_impl: dispatch schedule_task via asyncio.create_task so the HTTP request returns immediately. WatchScheduler.schedule_task awaits _execute_task inline (full re-ingest can take many seconds); keeping the trigger truly fire-and-forget matches the documented contract. - _resolve_task: document the deliberate 404-for-no-permission collapse (avoids leaking task existence to unauthorized callers across tenants). - by-id route ?to_uri= Query: expand description to warn that a wrong cross-key value blocks the operation with 400. MCP (`mcp_endpoint.py`): - list_watches: drop dead `try/except PermissionDeniedError` — WatchManager .get_all_tasks silently filters and does not raise. Replace with a comment pointing at the filtering line. - cancel_watch: rename captured-but-unused `ok` to `_` and clarify the inline comment to state that the return value is intentionally ignored (idempotent on the race-removal case). CLI (`commands/watch.rs`): - Extract `classify_key` and `validate_interval_minutes` as pure helpers, unit-tested directly. The previous "boolean predicate" test gave false coverage — would have passed even if the guard was removed. - classify_key now rejects `Viking://` (wrong case) and other-scheme `://` inputs (e.g. `http://`) with a clear Parse error, instead of silently treating them as task_ids that 404 server-side. Tests: - test_full_lifecycle and test_trigger_by_uri: fix monkeypatch signature (`(self, task_id)` instead of `(task_id)`) — class-level setattr binds self. Wait on an asyncio.Event so the assertion does not race the background asyncio.create_task started by the new fire-and-forget trigger implementation. Addresses: comments 9, 10, 11, 12, 13, 14, 15, 16 on PR #2110. * fix: address third round of Copilot review on PR #2110 REST router (`watches.py`): - Hold strong references to fire-and-forget trigger tasks. The event loop only keeps weak refs to tasks created via asyncio.create_task, so a discarded handle can be garbage-collected mid-execution and silently abort the refresh. Add a module-level `_BACKGROUND_TRIGGER_TASKS` set and use add_done_callback(set.discard) to release on completion. - list_or_get_watch: when `?to_uri=` and `active_only=true` are both supplied, also 404 paused tasks. Previously the to_uri branch returned any matching task regardless of active_only, which was inconsistent with the list-branch contract. Tests: - Add test_add_resource_rejects_negative_watch_interval covering the guard introduced in the prior commit. Addresses: comments 17, 18, 19 on PR #2110. * refactor(cli): nest watch under task; replace set-interval with update Two ergonomic changes after PR review feedback: 1. `ov watch *` → `ov task watch *`. The top-level `Watch` command moved under `TaskCommands` since both are forms of background work tracking (watch = recurring subscription, task = single-execution record). REST stays at /api/v1/watches — only the CLI surface is nested. 2. `set-interval <key> <minutes>` → `update <key> [flags]`. The PATCH endpoint already supported watch_interval / is_active / reason / instruction, but the CLI only exposed watch_interval. `update` now covers all four via flags, with at-least-one-flag enforced. `pause` / `resume` shortcuts are kept since they're the highest-frequency ops and `update --active=false` reads worse for them. Helper `build_update_body` is extracted as a pure function so the empty- flag, partial-flag, and interval-validation paths are unit-testable without an HTTP client. * fix: address qin-ctx review on PR #2110 REST router (`watches.py`): - UpdateWatchRequest: add field_validator rejecting watch_interval <= 0 at the request boundary. Previously REST accepted any float and forwarded to WatchManager.update_task, which deactivated the task and stored the bad cadence. A later resume (`is_active=true`) then failed inside update_task with ValueError that this router maps to 404 — misleading callers about the root cause. Now matches the CLI/MCP boundary checks so all three control planes agree on "non-positive is invalid". Test added covering 0, -1, and -42.5. CLI (`commands/watch.rs`): - Update the pause-hint error string from `ov watch pause` to `ov task watch pause` and update the helper docstring to reference `ov task watch update --interval` after the CLI nesting refactor. MCP (`mcp_endpoint.py`): - Refresh the watch-management section comment: mention `ov task watch *` (not `ov watch`) and `update --interval` (not the removed `set-interval` verb), keeping MCP-side guidance aligned with the shipped command names. Addresses: 3 review comments from @qin-ctx on PR #2110 (1 blocking, 2 non-blocking). |
||
|
|
8f59d8300b |
fix(server): serve favicons under /mcp/ for connector-relative clients (#2069)
The `/mcp` endpoint is registered as a single Starlette `Route` (exact-path match `^/mcp$`), so any sub-path like `/mcp/favicon.ico` falls through to the default JSON 404 handler. Some MCP clients (e.g. claude.ai connector cards) resolve the connector icon relative to the connector URL rather than the origin, which left those clients showing a generic placeholder. Register `/mcp/favicon.ico`, `/mcp/favicon.png`, and `/mcp/apple-touch-icon.png` as additional entries in `_favicon_files`, serving the exact same files already used at the origin root. The new routes are registered before the `/mcp` Route in `app.routes`, and the exact-path regexes never overlap, so they cannot intercept real MCP traffic. |
||
|
|
cd72f9182e |
feat(observability): dump HTTP query, request body, and response body to trace spans (#2052)
Add an opt-in middleware that attaches the request and response bodies (truncated, content-type filtered) onto the active OpenTelemetry root span, and surface the URL query string as `url.query`. Off by default — bodies may contain secrets and high-cardinality content; enable via `server.observability.dump_body.enabled` and bound payload size with `max_bytes`. The dump middleware is registered before the HTTP observability middleware so it nests inside the trace span (Starlette executes later-registered middleware first). Streaming, multipart, and binary content types are skipped, and any capture failure is swallowed so the request path is never affected. Co-authored-by: chenpengfei <chenpengfei@bytedance.com> |
||
|
|
9d36b2fd83 |
feat(console): 增加 Usage/Audit Dashboard BFF (#2016)
* feat(console): add usage audit dashboard BFF * feat(console): add usage audit retention config * docs(console): remove local usage audit design doc |
||
|
|
7d5fa62398 |
feat(oauth): native OAuth 2.1 authorization for MCP clients (#1870)
* feat(oauth): hand-sewn OAuth 2.1 M1+M2 (config, JWT, storage, /oauth/token, JWT discriminator)
Snapshot before evaluating migration to mcp.server.auth SDK provider. The
hand-rolled HS256 JWT implementation in openviking/server/oauth/jwt.py is
the main candidate for replacement: its surface area is small but it would
require careful crypto review by maintainers, while the official MCP SDK
already ships an OAuth provider wired into FastMCP.
Included so far:
- OAuthConfig + integration into OpenVikingConfig (default disabled)
- openviking/server/oauth/{jwt,storage,otp,router}.py
- POST /oauth/token (authorization_code + refresh_token, PKCE S256, RFC 6749 errors)
- JWT discriminator in resolve_identity (fail-closed; ResolvedIdentity.from_oauth)
- WWW-Authenticate Bearer hint on /mcp 401 (RFC 9728)
- 49 OAuth-specific unit/integration tests (all passing)
Not yet implemented (M3 / MVP gap):
- /oauth/register (DCR), /oauth/authorize (HTML + OTP submit), well-known metadata
- POST /api/v1/auth/otp REST endpoint
* refactor(oauth): switch to mcp.server.auth SDK provider, drop hand-sewn JWT
Replaces the hand-rolled HS256 JWT signer / token endpoint / DCR with
the OAuth 2.1 surface shipped in mcp.server.auth. We supply a Provider
that adapts the existing OAuthStore (SQLite) to the SDK Protocol, plus
two custom routes the SDK doesn't own: an OTP-entry HTML page (the URL
provider.authorize() returns) and POST /api/v1/auth/otp for issuing
OTPs against an existing API key.
Net result: all OAuth crypto is now the SDK's responsibility (PKCE
S256, redirect_uri matching, error formatting). The OpenViking-side code
contains zero cryptography — access tokens are opaque random strings
prefixed with `ovat_` and looked up in SQLite by SHA-256 hash. Refresh
tokens, auth codes, OTPs use the same scheme.
Highlights:
- openviking/server/oauth/provider.py: OpenVikingOAuthProvider implements
the 8-method SDK Protocol, including subclassing AuthorizationCode /
RefreshToken / AccessToken to pin (account_id, user_id, role) per
token. Refresh-token replay triggers per-user chain revocation.
- openviking/server/oauth/storage.py: adds oauth_access_tokens and
oauth_pending_authorizations tables; peek_auth_code / peek_refresh
for non-destructive lookups; revoke_user_tokens cascades all OAuth
state for an (account, user) pair when a key is rotated.
- openviking/server/oauth/router.py: minimal authorize page (inline
HTML with frame-ancestors 'none') + OTP endpoint authenticated via
existing get_request_context dependency.
- openviking/server/auth.py: replaces JWT discriminator with prefix
match + provider.load_access_token; still fail-closed.
- openviking/server/app.py: mounts SDK routes via create_auth_routes
alongside our authorize-page + OTP routes.
- Deletes openviking/server/oauth/jwt.py and tests/server/oauth/test_jwt.py.
Tests: 32 passing, including a full DCR -> OTP -> authorize page ->
token-exchange -> /mcp lookup happy path, refresh rotation, and replay
detection. Existing test_auth.py regression unchanged.
Phase 1 still missing for full Claude.ai connectivity:
- WWW-Authenticate hint already present on /mcp 401 (from M2)
- /.well-known/oauth-protected-resource (RFC 9728) — not currently
emitted by the SDK; small custom route still TODO.
* docs(oauth): rewrite design doc to reflect mcp.server.auth SDK approach
The earlier draft described a hand-sewn HS256 JWT plan; the implementation
took a different route after discovering mcp.server.auth ships a complete
RFC 6749 / 7591 / 8414 server. Updated to reflect:
- SDK owns the protocol surface (DCR, /authorize parsing, /token, metadata,
PKCE, redirect_uri matching, error codes).
- OpenViking only contributes a Provider implementation, the OTP-entry
HTML page, and POST /api/v1/auth/otp.
- Tokens are opaque (ovat_ / ovrt_ / ovac_ prefixes) — no JWT, no crypto
on our side.
- Implementation status: M1/M2/M3 done; only RFC 9728 protected-resource
metadata + reverse-proxy issuer derivation remain for full Claude.ai
end-to-end connectivity.
* feat(oauth): add /.well-known/oauth-protected-resource (RFC 9728)
The /mcp 401 path already advertises this URL via WWW-Authenticate
Bearer resource_metadata="...", but the endpoint itself didn't exist —
clients fetched it and got a 404, which silently broke the discovery
chain even though /.well-known/oauth-authorization-server worked. Wire
up the resource metadata document so the full RFC 9728 → RFC 8414
discovery chain works end-to-end.
Uses mcp.shared.auth.ProtectedResourceMetadata pydantic model. Reads
X-Forwarded-Proto/Host so the published resource URL matches what the
client used (matches our existing WWW-Authenticate behavior).
Cache-Control: max-age=3600 — metadata is stable across requests.
* feat(console): add OTP issuance button in Settings panel
Adds a "Get OTP" button under the Settings panel of the 8020 web
console. Clicking it issues an OAuth OTP via the user's existing API
key (already loaded into sessionStorage) and displays it inline with
a copy-to-clipboard button.
Replaces the previous workflow of users having to:
curl -X POST -H "X-Api-Key: $KEY" http://1933/api/v1/auth/otp
…with a single button-click flow that the user can reach from any
machine with a browser.
Wires:
- console/app.py: new POST /console/api/v1/ov/auth/otp proxy route,
forwarding to upstream /api/v1/auth/otp. Not gated by write_enabled
since OTP issuance is an authentication artifact, not data mutation.
- index.html: new OAuth section in the Settings panel with otpBox
(hidden until OTP is generated) and a Copy button.
- app.js: getOtpBtn click handler calls callConsole, otpCopyBtn copies
to clipboard. Clear failure messages when the user has no API key
loaded yet.
This is the lightweight half of the Console-OAuth integration. The
fuller "same-origin auto-authorize" flow (Phase 2) — where the
authorize page detects sessionStorage and submits the OTP form
automatically — is still TBD and will reuse this proxy route.
* feat(oauth): device-flow style authorize page + console verify form
Pivots the OTP flow direction so the UX matches OAuth 2.0 Device
Authorization Grant (RFC 8628) more closely:
Old (push): user goes to console -> Get OTP -> copy -> paste in
client's authorize page -> submit -> redirect.
New (pull): client's authorize page DISPLAYS a 6-char code -> user
types it into the console verify form -> page polls -> redirect.
This removes one tab switch and aligns with how users mentally model
authorization ("I'm approving the request shown over there from
where I'm already signed in"). The legacy POST /api/v1/auth/otp +
"Get OTP" button are kept under a collapsed details element for any
scripted/CLI flows that still drive the older pattern.
Also wires OPENVIKING_PUBLIC_BASE_URL env var as the highest-priority
public origin override, used consistently by:
- /.well-known/oauth-protected-resource
- WWW-Authenticate header
- authorize page links
- SDK issuer at app start.
Server changes:
- storage.py: oauth_pending_authorizations gains display_code,
verified, verified_account_id/user_id/role columns; new
find_pending_by_display_code + mark_pending_verified.
- provider.authorize() now generates display_code at pending creation
and returns the page URL.
- router.py:
* GET /oauth/authorize/page — renders the code + same-origin quick-
authorize panel (sessionStorage detection, but click still required
so authorization is never silent).
* GET /oauth/authorize/page/status — polled by the page until verified;
response carries the redirect_url with auth_code on approval.
* POST /api/v1/auth/oauth-verify — authenticated; binds caller
identity to a pending row (decision=approve|deny).
Console changes:
- Settings panel: new "Authorize an MCP client" section with code input
and Authorize/Deny buttons. Legacy "Get OTP" still available under
details.
- console proxy gains POST /console/api/v1/ov/auth/oauth-verify.
Tests: 38 OAuth tests passing, including a full device-flow happy path,
deny path, idempotency (one-shot pending), unknown-code rejection,
status-410 on consumed/expired, refresh rotation, OPENVIKING_PUBLIC_BASE_URL
override, and X-Forwarded-* fallback.
* docs(oauth): add 11-oauth guide + Caddy/nginx templates + .env-driven compose
Adds a top-level OAuth 2.1 guide (zh/en) covering the production path
end-to-end. Opens with a 5-step recommended setup so readers don't have
to wade through the rationale before they can deploy. Drops the "MCP"
qualifier from the doc name — OAuth 2.1 here is generic and serves any
OAuth client, not just MCP.
- docs/{en,zh}/guides/11-oauth.md: new. Recommended setup at the top,
then background, full device flow, HTTP-local vs HTTPS-production
deployment, Caddy + nginx templates, docker-compose with the shipped
Caddy service, curl walkthrough, config reference, troubleshooting.
- docker-compose.yml: replace the prior PR's commented-out hint with a
single OPENVIKING_PUBLIC_BASE_URL var (read by both the openviking
service and an optional Caddy reverse-proxy service that's also
shipped commented-out). Same env var drives Caddy via
{$OPENVIKING_PUBLIC_BASE_URL}, so the public domain is configured
once in .env.
- docs/{en,zh}/guides/06-mcp-integration.md: replace the "OAuth Proxy
(planned, use community Cloudflare Worker)" section with a short
pointer to the new 11-oauth guide. The community proxy is still
mentioned as an alternative.
Same env-variable design also matches what the MCP add_resource tool
expects (it already reads OPENVIKING_PUBLIC_BASE_URL), so deployments
get a single source of truth for the public address.
* fix(oauth): read API key from localStorage on authorize page
The same-origin "Quick authorize" panel was reading sessionStorage,
which is per-tab. Since the OAuth authorize page opens in a different
tab from the console, the panel never showed up even when the user was
signed in.
The console persists the API key in localStorage as well (key
"ov_console_api_key" — see static/console_settings.js's
LEGACY_API_KEY_STORAGE_KEY) for cross-tab use, and that copy is what
the authorize page should consult.
Switch the page JS to localStorage first, fall back to sessionStorage
for resilience. No console-side change needed; the localStorage entry
has been written by the console all along.
* docs: add public access guide + default port 1934 aggregated proxy
- Add Caddyfile with :1934 HTTP aggregated proxy (merges 1933+8020)
- Enable Caddy service by default in docker-compose.yml on port 1934
- Add docs/{en,zh}/guides/12-public-access.md with full HTTPS setup guide
- Simplify 11-oauth.md: replace inline reverse proxy config with refs to 12
- Add HTTPS requirement callout to OAuth recommended setup
- Update 03-deployment.md to mention port 1934 as recommended entry point
* fix(oauth): address Copilot review + ruff format
- Update oauth_config.py docstrings to describe opaque tokens, not JWT
(we switched away from JWT during implementation)
- Remove unused authorize_rate_limit_per_min config field — was never
enforced anywhere in router/storage, dead config misled operators
- Wrap all OAuthStore read paths in self._lock (matching writes); the
shared sqlite3.Connection with check_same_thread=False is not safe
for concurrent cursor use across threads
- Clarify provider.exchange_refresh_token comment that replay revokes
the entire (account, user) family, not just the (client, account,
user) chain — broader blast radius is intentional
- ruff format: 8 files reformatted to satisfy CI lint
* perf(docker): add cargo + ccache cache mounts to py-builder stage
The two heavy RUN steps in py-builder (uv sync + maturin build) re-execute
on every Python source change because the upstream COPY layer for openviking/
invalidates the cache. Each rerun was ~510s + ~115s ≈ 10 min of wasted work
even though Rust/C++ source was unchanged.
Add BuildKit cache mounts so cargo and the C++ engine compilation can skip
work whose inputs are unchanged:
- Mount /cargo-target, cargo registry, and cargo git so cargo's incremental
build artifacts persist across layer reruns. Pin CARGO_TARGET_DIR so the
path stays stable when uv builds wheels in ephemeral isolated tempdirs.
- Install ccache and prepend /usr/lib/ccache to PATH so cmake (which calls
shutil.which("gcc")) resolves the ccache wrapper. ccache is path-agnostic,
so it benefits the cmake_build subdir even though setup.py recreates it
in a fresh tempdir each wheel build.
- Mount /root/.ccache so the ccache hash store persists across reruns.
Expected: hot rebuilds on Python-only changes drop step 15 from ~510s to
~60-120s (uv wheel packaging overhead remains; cargo + g++ skip on cache hit).
* perf(docker): drop redundant second maturin build step
The second RUN step in py-builder built ragfs-python a second time and
extracted its .so into the installed openviking package. This was
redundant: setup.py's build_ragfs_python_artifact() already runs maturin
during step 15 (uv sync --no-editable), and because build_meta passes
'bdist_wheel' through PEP 517, _should_require_ragfs_artifact() returns
True and the build fails closed if maturin can't produce ragfs_python.so.
The .so is then bundled into the wheel via package_data and installed
into /app/.venv on wheel install. The second step's only effect was to
overwrite the same file, costing ~115s per build.
Verified after the fact by inspecting the installed venv and importing
ragfs_python in the runtime container.
* feat(oauth): bind OAuth token lifetime to authorizing API key
Previously OAuth tokens lived independently of the API key that authorized
them. Rotating a user's key did not invalidate already-issued OAuth access /
refresh tokens, so a compromised key remained dangerous even after rotation.
Tie every OAuth token to the SHA-256 fingerprint of the API key whose holder
authorized it:
- APIKeyManager grows get_user_key_fingerprint(account_id, user_id) ->
sha256(stored_key_value). The stored value is whatever sits in
user_info["key"] (plaintext key or argon2id hash), written once on
create / regenerate and never mutated in place, so the fp is stable per
key-generation and changes the moment regenerate_key runs.
- OAuth storage gains an authorizing_key_fp column on oauth_codes,
oauth_pending_authorizations (verified_key_fp), oauth_refresh_tokens, and
oauth_access_tokens. ALTER TABLE migration guarded by PRAGMA table_info
for dev DBs that predate the field.
- Provider data classes thread the fp through authorize ->
exchange_authorization_code -> _issue_token_pair, and refresh rotation
preserves it from the consumed token's record.
- Router endpoints capture the caller's current fp at the only two
identity-binding moments: /api/v1/auth/otp (caller) and
/api/v1/auth/oauth-verify (verifier). If the manager returns None
(ROOT key, trusted-mode identity, or removed user), refuse to issue
OAuth state -- there is no key whose lifecycle we could honor.
- auth.py:_try_resolve_oauth_token recomputes the user's current fp on
every OAuth bearer auth and demands strict equality via
hmac.compare_digest. NULL / empty / mismatch all fail closed with a
401 telling the client to re-authorize.
Crypto notes: sha256 over a 256-bit-random API key (or its argon2id hash)
is preimage-safe, so an oauth.db leak does not reveal the API key. No new
secret material introduced; the fp is derived deterministically from data
that already exists.
Tests: 3 new lifecycle tests in test_auth_integration (rotation rejected,
user-removed rejected, missing-fp fail-closed), 3 new router tests
(no-fp caller / verifier rejected, fp recorded on access + refresh), 2 new
APIKeyManager tests (fp changes on rotate / vanishes on remove).
Pre-existing inserts in test_storage updated to pass _FP. 82/82 OAuth +
APIKeyManager tests pass.
* docs(oauth): document OAuth lifetime ≤ authorizing key lifetime
The fingerprint binding landed in the previous commit; users need to know
that key rotation now auto-invalidates derived OAuth tokens (no separate
revoke step) and that ROOT / trusted-mode identities cannot issue OAuth.
Updates both en and zh under docs/guides/11-oauth.md, replacing the
"operator should also revoke ..." paragraph with the new automatic
behavior + brief note on the SHA-256 fingerprint scheme.
* fix(oauth): close 4 review findings on token lifecycle
External security review of #1870 surfaced four real gaps in the OAuth
implementation. All four directly affect the lifecycle / privilege model.
P1: role downgrade did not invalidate OAuth tokens
set_role rewrites user_info["role"] without touching user_info["key"],
so the SHA-256 fingerprint binding stays valid and an ADMIN demoted to
USER continues to resolve as ADMIN. Refresh tokens keep minting fresh
ADMIN access tokens. Fixed in two places:
- auth.py:_try_resolve_oauth_token re-fetches Role.get_user_role and
rejects when the embedded role outranks the current role.
- provider.exchange_refresh_token gets a role_resolver callback (wired
in app.py to api_key_manager.get_user_role) and applies the same
gate before consuming a refresh.
Promotion remains harmless — the embedded lower privilege is still
authorized, only downgrades trigger rejection.
P1: confidential client secrets were never enforced
provider.get_client returned client_secret=None regardless of the
stored hash; the MCP SDK's ClientAuthenticator skips secret validation
when the returned client has a falsy secret, silently allowing
client_secret_basic / client_secret_post clients to authenticate with
only client_id. Real MCP clients all use "none" + PKCE per RFC 8252
§8.4 anyway, so register_client now rejects non-"none" auth methods at
DCR. Native/desktop apps can't keep secrets — PKCE is the actual
proof-of-possession.
P1: OAuth tokens could mint new OAuth grants
/api/v1/auth/otp and /api/v1/auth/oauth-verify accepted any caller
resolved through get_request_context, including identities resolved
from OAuth bearers. A stolen 1h access token could call oauth_verify
with its own pending row and walk away with a 30d refresh-token
chain — privilege time-extension. RequestContext now carries
from_oauth (mirroring ResolvedIdentity.from_oauth) and both endpoints
reject from_oauth=True with 403, forcing primary auth.
P2: GC erased refresh-token replay tombstones
gc_expired deleted "WHERE expires_at < ? OR consumed = 1" every
minute. After GC, is_refresh_known_but_consumed could not distinguish
a replay from an unknown token and exchange_refresh_token never fired
revoke_chain — defeating RFC 9700 §4.14 family revocation for late
replays. GC now keeps consumed refresh rows until their natural
expires_at; storage cost bounded by the 30d max refresh TTL.
Also adds from_oauth field to RequestContext and propagates from
ResolvedIdentity in get_request_context.
Tests: 7 new (role downgrade rejection in bearer auth + refresh path,
role promotion is harmless, confidential DCR rejected, from_oauth
rejected at OTP and oauth-verify, refresh tombstone preserved across
GC). Pre-existing test_oauth_root_can_be_used and
test_dcr_registers_client updated to match the stricter contract.
89/89 OAuth + APIKeyManager tests pass.
* fix(oauth): downgrade confidential DCR to public instead of rejecting
The previous P1.2 fix rejected DCR when token_endpoint_auth_method was
not "none", reasoning that we never enforce client_secret server-side
so accepting confidential auth methods would be a silent security
downgrade. That is the right invariant — but the rejection broke real
clients: the OAuth 2.0 default for token_endpoint_auth_method is
"client_secret_basic", and at least Claude Desktop relies on the SDK to
fill in defaults rather than explicitly setting "none". DCR for those
clients started returning 400 even though they would work fine with PKCE
(which they all use anyway).
Soft-failure design instead: accept any registered auth method, but
overwrite the stored value to "none" and log a warning. The end-state
is identical to the rejection path — every client is treated as
public+PKCE, no secret is ever stored or enforced — but Claude Desktop's
DCR no longer blows up.
Updates the test from asserting 400 to asserting that a confidential
registration is silently downgraded: stored auth_method == "none",
client_secret_hash is None.
* delete(docs): remove error file
* docs(zh): sync 03-deployment.md with English version
|
||
|
|
ac3346422a |
feat(rebuild): add rebuild api scaffold (#1592)
* feat(admin): add rebuild api scaffold
feat: add admin rebuild API
fix: harden admin rebuild execution
feat(cli): add rebuild command support
fix(rebuild): support namespace rebuild routing
refactor(rebuild): unify memory semantic rebuild mode
refactor(rebuild): move http endpoint to content route
fix(rebuild): skip root namespace vectorization
fix(rebuild): harden namespace classification
refactor: rename rebuild api to reindex
refactor: rename reindex executor module
refactor(reindex): remove unused reason field
* fix(reindex): tighten namespace URI handling
Share segment-based Viking URI classification across context inference and reindex execution, add skill namespace support, and require root reindex requests to select an account.
* refactor: reuse indexing pipeline in reindex
* Revert "refactor: reuse indexing pipeline in reindex"
This reverts commit
|
||
|
|
f4c720291c |
fix(server): 延迟初始化失败时退出进程 (#1893)
* fix(server): 延迟初始化失败时退出进程 _deferred_init 失败后 app.state.api_key_manager 永不设置, 导致 /health 返回 200 但所有认证请求返回 500 的静默故障。 改为 os._exit(1) 恢复原有的故障信号:进程退出,Docker 重启。 Refs: #1892 * fix(server): harden deferred init callback --------- Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com> |
||
|
|
660835a09e |
fix(server): 分阶段 lifespan 使 /health 在初始化期间可响应 (#1878)
* fix(server): 分阶段 lifespan 使 /health 在初始化期间可响应
将 lifespan 重构为两阶段:
- yield 前:仅执行 essentials(set_service、metrics、tracing、MCP)
- yield 后:service.initialize() 和 APIKeyManager 作为后台任务运行
/health 端点(无状态)现在在服务器接受连接后立即可用,
不再被 service.initialize() 阻塞。缓慢的集合恢复等操作
在后台执行而不影响存活探针。
/ready 端点新增 _initialized 检查:
初始化未完成时返回 503 {status: not_ready, reason: initializing},
完成后保持原有行为。
Refs: #1793
* fix(server): 确保 _deferred_init 对所有 service 执行
将 _deferred_init 的触发条件从 owns_service 改为 service is not None。
预初始化的 service 传入 create_app() 时,owns_service 为 False,
导致 APIKeyManager 永不初始化,所有认证请求返回 500。
service.initialize() 是幂等的(已初始化时早返),可安全重入。
* fix(server): /ready 端点精确捕获 RuntimeError 替代裸 Exception
get_service() 仅在 _service 为 None 时抛 RuntimeError。
裸 except Exception 会错误吞掉 KeyboardInterrupt 等不应捕获的异常。
* feat(server): 为延期初始化任务添加即时异常日志回调
通过 add_done_callback 在后台初始化任务失败时立即记录异常,
而非等到 shutdown 时才捕获。提升初始化失败的可观测性。
* style(server): ruff 格式化 deferred init 回调
|
||
|
|
33113eb057 |
feat: serve tight favicon variants for API server and docs site (#1879)
* feat(server): serve favicon and apple-touch-icon at root Browsers and MCP clients (claude.ai, Claude Desktop) auto-fetch /favicon.ico and /apple-touch-icon.png to display a server icon. The 1933 server previously returned JSON 404s for these paths, leaving connectors with a generic placeholder. Add small route handlers that serve OV-branded icons from the existing console/static directory (already shipped as package data). Also wire the console index.html to the new icons. * feat(docs): use tight favicon variants for VitePress site The docs site previously pointed `<link rel="icon">` at the same 1000x1000 ov-logo.png used by the in-page nav, so the tab favicon rendered visibly small inside heavy whitespace. Reuse the trim+square favicon variants generated for the API server (favicon.ico, favicon-32.png, apple-touch-icon.png) under docs/images/ and wire them into the VitePress head config. The in-page nav logo still uses the original PNG (whitespace looks fine in that context). |
||
|
|
bb515b1007 |
feat(openviking): Add User-Level Skill Privacy Configuration Capability (#1745)
* 增加用户隐私信息,skill 提取 * 增加用户隐私信息,skill 提取 * 字段提取 * privacy 配置cli 指令 * doc * doc |
||
|
|
ccad9c5e0e |
feat(server): native MCP endpoint with 9 tools aligned to VikingBot (#1738)
* feat(server): add native MCP endpoint at /mcp Serve 5 MCP tools (search, read, store, forget, health) directly from the OV FastAPI server via streamable HTTP transport. This eliminates the need for the Node.js MCP subprocess — the plugin's .mcp.json now points to the server URL instead of spawning a process. Identity headers (X-OpenViking-Account/User/Agent) are propagated to service-layer calls via contextvars ASGI middleware. * fix(mcp): disable DNS rebinding protection for reverse proxy compatibility MCP SDK auto-enables host validation for localhost, rejecting requests with external Host headers (e.g. from Cloudflare/Nginx reverse proxy). * fix(mcp): reuse auth.resolve_identity for MCP endpoint authentication MCP endpoint previously had no authentication — requests fell through with default/default identity. Now delegates to the same resolve_identity used by all REST routes, so auth_mode, API key validation, and identity resolution are handled identically. * fix(mcp): fix import path for TextPart in store tool openviking.session.parts does not exist; the correct module is openviking.message.part. * fix(mcp): store tool now creates a new session and commits immediately Each store call creates a unique session, adds the message, and commits right away so memories are extracted and searchable without waiting for a token threshold. * chore: add mcp>=1.27.0 dependency for native MCP endpoint * fix(mcp): align search/forget tools with REST API, fix forget crash - Remove SEARCH_TARGETS and per-scope loop; use single service.search.find(target_uri="") call matching REST API behavior - Fix forget crash: FSService has no delete(), use rm() instead - Replace fragile _is_memory_uri() substring check with ContextType - search tool: replace scope param with target_uri for direct passthrough - Work directly with FindResult/MatchedContext objects instead of dict-munging via to_dict() * fix(mcp): fail-closed on missing identity, remove unused Role import - _get_ctx() now raises UnauthenticatedError instead of defaulting to ROOT when identity contextvar is not set - Remove unused Role import - Clean up comments in create_mcp_app * test(mcp): add unit tests for MCP endpoint tools 17 tests covering all 5 MCP tools and identity propagation: - _get_ctx: returns context when set, raises UnauthenticatedError when not - health: healthy/unhealthy responses - search: no results, with resource, with target_uri - read: nonexistent URI, directory listing, batch reads - store: user and assistant roles - forget: input validation, non-memory guard, URI deletion, query fallback - Route registration: /mcp route exists in app * docs(mcp): update integration guide with verified platforms and correct tools - Add verified platforms table (Claude Code, ChatGPT/Codex, Claude.ai, Manus, Trae) - Document authentication (X-Api-Key / Bearer token) - Add Claude.ai OAuth proxy (MCP-Key2OAuth) instructions - Update tool table to match actual implementation (search, read, store, forget, health) — remove stale tool names - Reorganize client config: generic first, then platform-specific * feat(mcp): expand to 7 tools aligned with vikingbot, split read/list Align MCP tool surface with vikingbot/agent/tools/ov_file.py: - Split read/list: read is file-only with semaphore(10) concurrency; list is directory-only with recursive support - store: accept batch messages[] (was single text), matching VikingMemoryCommitTool - search: add min_score parameter (default 0.35), matching VikingSearchTool - add_resource: new tool for adding files/URLs to resources - Use @mcp.tool(name="list") to avoid shadowing Python builtin 7 tools: search, read, list, store, add_resource, forget, health * feat(mcp): add grep and glob tools, update docs to 9 tools Add grep (multi-pattern regex search) and glob (file pattern matching) MCP tools to align with VikingBot's full tool surface. Update EN/ZH integration docs to reflect all 9 tools with correct parameters. * fix(mcp): store schema, forget safety, remove memories-only restriction - store: use Pydantic StoreMessage model so MCP schema includes required role/content field definitions (was bare dict[str, str]) - forget: remove query parameter entirely — deletion requires exact URI, use search tool first to find candidates - forget: remove /memories/ path restriction, allow deleting any URI * docs(mcp): update forget tool description — exact URI only, no query * fix(mcp): rename list_dir to ls, add forget safeguard, use Bearer in docs - Rename list_dir → ls (MCP tool name stays "list") to avoid confusion with "only lists directories" - Add safeguard to forget tool description: irreversible, requires user confirmation - Docs: use Authorization: Bearer in all examples (standard, consistent with OAuth proxy flow) - Fix ruff format on mcp_endpoint.py and test_mcp_endpoint.py |
||
|
|
ac9f679a0b |
fix(api): 统一错误 envelope 与会话元数据 (#1764)
* fix(api): standardize error envelopes and session metadata * fix(session): update archive metadata on commit |
||
|
|
64682ae189 |
feat(encryption): make apikey hash encryption as single switch (#1736)
* feat(encryption): make apikey hash encryption as single switch * feat(encryption): add break change note |
||
|
|
17d2c5603e |
feat(observability): unify observability context && support otel && etc. (#1666)
* feat(observability): unify OTLP metrics export, log/trace context, and telemetry bridging - - Add OTLP metrics http/grpc exporter that pushes MetricRegistry snapshots - - Decouple telemetry response payload from telemetry collection; always finish() and bridge summary to metrics - - Unify observability config under server.observability (metrics/traces/logs siblings); update ov.conf.example and docs (zh/en) - - Improve log/trace correlation via structured context injection - - Add/adjust tests for exporter lifecycle, config loader, metrics/telemetry runtime - BREAKING CHANGE: remove legacy telemetry.* config path; use server.observability.* * feat(observability): import Status/StatusCode for LogToSpanEventFilter * feat(observability): fix check issue * feat(observability): format code --------- Co-authored-by: MaojiaSheng <shengmaojia@bytedance.com> |
||
|
|
85986a91bd |
fix: apikey security: API Key 管理重构与安全增强 (#1686)
* fix: apikey security * fix: apikey security * fix: apikey security * fix: apikey security --------- Co-authored-by: openviking <openviking@example.com> |
||
|
|
495b7a7616 | feat: support trusted admin tenant management (#1616) |