merge: sync upstream main and web-studio file manager into new-frontend #5
merge: sync upstream main and web-studio file manager into new-frontend #5feichuans wants to merge 75 commits into
Conversation
feichuans
commented
Apr 11, 2026
- Upstream: 68 PRs (Feature/memory opt volcengine/OpenViking#1159~feat(bot): Add disable OpenViking config for channels. volcengine/OpenViking#1352) covering CI, security, memory, session, storage
- web-studio: Viking File Manager with tree/list/preview, theme rework, sidebar refinement
…efs (volcengine#1211) * fix: PID lock recycle, recall threshold bypass, orphaned compressor refs 1. process_lock: verify PID is actually OpenViking on Linux by checking /proc/{pid}/cmdline. Prevents false DataDirectoryLocked when PIDs are recycled to unrelated processes after crash (Fixes volcengine#1088). 2. memory-ranking: add scoreThreshold param to pickMemoriesForInjection and filter non-leaf items below threshold. Previously low-scoring memories bypassed recallScoreThreshold when supplementing leaves (Fixes volcengine#1106). 3. compressor: catch FileNotFoundError separately in _merge_into_existing, clean up orphaned vector records so they are not retried indefinitely (Fixes volcengine#1048). * style: ruff format process_lock.py --------- Co-authored-by: JasonOA888 <JasonOA888@users.noreply.github.com>
…olcengine#769) (volcengine#792) Repeated memory writes enqueue the same parent directory for .overview/.abstract regeneration, causing redundant VLM work. Skip duplicate enqueues for the same (account, user, agent, uri) within 45s; resource/session semantic paths unchanged. Fixes volcengine#769 Made-with: Cursor
…lcengine#1228) Add README link for Claude Code Memory Plugin example in all language variants (EN, CN, JA) and add Chinese documentation for the plugin.
…lcengine#1226) Move redo recovery to a background task so the server starts without waiting for potentially slow VLM calls. Add 60s timeout to extract_long_term_memories to prevent indefinite hangs on individual redo tasks. Clean up the redo task on stop(). Fixes volcengine#1222 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Replace to_string_lossy() with to_str() to preserve valid UTF-8 filenames (Chinese, Japanese, special characters) instead of silently corrupting them. Non-UTF-8 paths now return a clear Error::InvalidPath instead of garbled output. Fixes volcengine#1018 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
…es (volcengine#1223) Add tests covering the fix in PR volcengine#1163 where decrypt() raised 'Ciphertext too short' on plaintext files shorter than 4 bytes. Tests added: - test_decrypt_empty_plaintext: raw b'' returns b'' - test_decrypt_short_plaintext_less_than_4_bytes: b'X', b'AB', b'ABC' return as-is - test_decrypt_magic_prefix_without_full_header: b'OVE1' raises CorruptedCiphertextError Co-authored-by: yc111233 <yc111233@users.noreply.github.com>
fix: request wait telemetry id fix: register request wait before enqueue add log
* reorg: rewrite agfs with rust, and named with ragfs, keep License * reorg: rewrite agfs with rust, and named with ragfs, keep License * reorg: rewrite agfs with rust, and named with ragfs, keep License * reorg: rewrite agfs with rust, and named with ragfs, keep License * reorg: rewrite agfs with rust, and named with ragfs, keep License * reorg: rewrite agfs with rust, and named with ragfs, keep License * reorg: rewrite agfs with rust, and named with ragfs, keep License * reorg: rewrite agfs with rust, and named with ragfs, keep License * fix: grep level limit * fix: grep root * fix: import error * fix: rust code optimazation * fix: CI error * fix: CI go mod cache * fix: grep level limit * fix: CI --------- Co-authored-by: openviking <openviking@example.com>
… private-network SSRF (volcengine#1133) * Harden HTTP resource ingestion against private-network SSRF * chore(ci): retrigger checks * style: fix resource service import order
…nt infinite hang (volcengine#1235) When Phase 2 of a previous archive crashes after writing messages but before writing .done or .failed.json, the next archive's memory extraction enters an infinite polling loop with no exit condition. Add a 5-minute deadline. On timeout, return False (treated as failure) so the caller writes .failed.json and the session is no longer stuck. Co-authored-by: yc111233 <yc111233@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ine#1243) * fix(bot): respect OPENVIKING_CONFIG_FILE even when file doesn't exist Previously, bot's config path resolution would fallback to ~/.openviking/ov.conf when OPENVIKING_CONFIG_FILE was set but the file didn't exist. This was inconsistent with server's behavior, which treats a missing env-specified config as an error. In container deployments with OPENVIKING_CONFIG_FILE=/app/ov.conf, this caused bot to potentially write auto-generated config to /root/.openviking/ov.conf instead of the intended /app/ov.conf, leading to config file path mismatches. Now bot respects the environment variable unconditionally, matching server's behavior and ensuring both components use the same config path. Fixes volcengine#1242 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(embedder): support dimension truncation for OpenAI-compatible models Previously, when users configured a dimension (e.g., 1024) for OpenAI-compatible embedding models that don't support the 'dimensions' API parameter, the system would fail with "dimensions is currently not supported" error. Additionally, when dimension was not configured, the config layer would return a hardcoded fallback of 2048, but the actual model might return a different dimension (e.g., 1024), causing dimension validation failures. This fix implements vector truncation in OpenAIDenseEmbedder: - Removes the 'dimensions' parameter from API calls (not supported by all models) - If user configures dimension=1024 and model returns 2048, truncates to 1024 - If no dimension is configured, uses model's native dimension without truncation - Applies truncation to both single and batch embedding operations This allows users to: 1. Use OpenAI-compatible models with custom dimensions via truncation 2. Control vector dimensions for storage optimization 3. Avoid dimension mismatch errors between config and actual embeddings Fixes volcengine#1238 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: volcengine#1232 --------- Co-authored-by: openviking <openviking@example.com> Co-authored-by: Claude <noreply@anthropic.com>
Include viking://agent/skills in the autoRecall search alongside user memories and agent memories. Skills stored in OpenViking are now auto-injected into context when relevant to the query. Fixes volcengine#1089 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
…to preve…" (volcengine#1265) This reverts commit dc15daf.
* feat(storage): volcengine vector db support sts token https://www.volcengine.com/docs/7139/1302258?lang=zh * feat(storage): volcengine vector db support sts token https://www.volcengine.com/docs/7139/1302258?lang=zh
replace the old one.
- Reduce OS matrix from 5 channels to 3 channels - Use ubuntu-latest, macos-latest, windows-latest instead of specific versions - Remove redundant ubuntu-arm and intel macos variants
…olcengine#1284) - Update provider registry to document MiniMax-M2.7 and MiniMax-M2.7-highspeed as the recommended models (replacing the outdated M2.1 example comment) - Add explicit note that MiniMax does not support system messages (they are merged into the first user message automatically) - Update README to advertise MiniMax-M2.7 and MiniMax-M2.7-highspeed as the recommended MiniMax models with configuration instructions - Add comprehensive unit tests (17 tests) covering: - Registry keyword matching for MiniMax-M2.7 and MiniMax-M2.7-highspeed - Model prefix resolution (minimax/MiniMax-M2.7) - System message merging for both LiteLLMProvider and OpenAICompatibleProvider - Edge cases: multiple system messages, no system messages, non-MiniMax models - MINIMAX_API_KEY environment variable and international API base URL Co-authored-by: octo-patch <octo-patch@github.com>
* 增加完整的一键评测脚本 * 增加完整的一键评测脚本
Implements a two-phase benchmark pipeline for evaluating mem0 on the LoCoMo long-term conversation dataset (10 samples, 1540 non-adversarial QA pairs). - ingest.py: imports LoCoMo conversation sessions into mem0, using sample_id as the userId namespace. All messages use "user" role with [SpeakerName]: prefix to preserve two-person dialogue structure. Temporal context is added via a [System] prefix on each session. - eval.py: sends QA questions to an OpenClaw agent backed by the openclaw-mem0 plugin. Restarts the gateway per sample to switch the active userId, verifies the correct user is loaded before running questions, then parallelizes questions within each sample using unique session keys. Parses session jsonl to collect accurate per-turn token usage. Optionally judges answers with a Volcengine ARK LLM. - delete_user.py: utility to clear mem0 memories for given user_ids. - README.md: documents prerequisites, ingest/eval parameters, output format, and per-sample run commands.
…it (volcengine#1327) - Only run full OS matrix (ubuntu-24.04, macos-14, windows-latest) on main branch pushes - PR branches use ubuntu-24.04 only to reduce runner wait time - Add max-parallel: 2 to limit concurrent job execution
* 修复语种问题 * 多user提交ov
Co-authored-by: openviking <openviking@example.com>
Add solution for health check tools report http 404 error
Track semantic and embedding re-enqueues as first-class queue metrics so observer output, wait_processed payloads, and telemetry summaries make retry loops visible before they escalate into hard errors.
…lcengine#1330) (volcengine#1333) Co-authored-by: yuanqianhe <yuanqianhe@lattebank.com>
* fix(resources): improve handling of resource imports and naming - Add source_name to file upload requests for preserving original filenames - Handle single-directory zip files by using their root directory directly - Support viking://resources as parent directory for imports - Split summarization for resources root imports into individual child items - Add tests for new resource import behaviors * style(tests): format test files with consistent line breaks Improve readability by applying consistent line breaks in test file patches and removing trailing whitespace
…onality - Added FileTree component for displaying directory structure. - Introduced VikingFileManager component to manage file operations and navigation. - Created hooks for fetching file system data and handling file previews. - Implemented API functions for interacting with the file system. - Normalized file system entries and URIs for consistent handling. - Updated routing to support file manager navigation with URI segments. - Enhanced UI components for better user experience in file management.
…e#1342) Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com>
* fix: make api_test more robust for CI environments - Add @pytest.hookimpl(optionalhook=True) for pytest-html hooks to fix compatibility issues - test_fs_read: skip test when AGFS service is not available - test_get_overview: skip test when overview file does not exist These changes ensure tests pass gracefully on CI servers where AGFS service may not be available or files may not exist. * ci: reduce max-parallel to 1 for better resource availability Reduce max-parallel from 2 to 1 to avoid waiting for multiple runners when GitHub-hosted runners are limited.
index_resource() always passed the default context_type="resource" to vectorize_directory_meta() and vectorize_file(), even when indexing memory directories (URIs containing /memories/). This caused all records created via the /api/v1/content/reindex endpoint to be tagged as "resource" instead of "memory", breaking stats aggregation (which filters on context_type="memory") and search scoping. Use the existing get_context_type_for_uri() helper to derive the correct context_type from the URI and pass it through to both vectorize_directory_meta() and vectorize_file(). Co-authored-by: yc111233 <yc111233@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs: fix docker deployment * reorg: remove third_party/agfs * feat(s3fs): add disable_batch_delete option for OSS compatibility Port of PR volcengine#1333 from Go version to Rust: - Add disable_batch_delete config option to S3Client - When enabled, use sequential single-object deletes instead of DeleteObjects - This is for S3-compatible services like Alibaba Cloud OSS that require Content-MD5 for DeleteObjects but AWS SDK v2 does not send it by default - Add documentation and config example for OSS * fix(s3fs): pass disable_batch_delete config from Python to Rust Add disable_batch_delete to the s3_plugin_config dict in _generate_plugin_config so that the Python config can properly control the Rust S3FS plugin's behavior. * reorg: remove third_party/agfs * reorg: remove third_party/agfs * change some docs * change some docs --------- Co-authored-by: openviking <openviking@example.com>
* fix(memory): handle string response from VLM when tools disabled - Fix AttributeError when VLM returns string instead of VLMResponse - Fix tuple creation bug with trailing comma causing double-nested tools array Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * update * chore: replace LGBTQ example with book club in entities.yaml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(memory): disable tools on extended iteration to prevent infinite loop When max_iterations is extended due to tool calls, disable tools for the additional iteration to ensure the extract loop terminates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(memory): handle out-of-bounds range from LLM extraction Clamp range values to valid message indices instead of skipping, to handle cases where LLM extracts incorrect ranges. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…engine#1350) Co-authored-by: openviking <openviking@example.com>
…l style - Progressive panel layout: 1-panel (root), 2-panel (folder/file view) - Move toolbar buttons into tree sidebar, breadcrumb into file list header - Replace table with flat list layout, size/date aligned to right - Remove page shell title, section headers, and preview bottom path bar - Folder icons: filled dark gray, selection highlight changed to gray - Sidebar: narrowed to 11rem, border replaced with shadow, rail removed - Full-bleed layout fused with system sidebar, no boundary gap - Clean up unused imports and header breadcrumbs from root layout
- Resizable file tree panel with drag handle (160-600px range) - Layout: grid-cols → flex for proper resize support - Fix tree expand height: replace max-h-[1000px] with grid-rows animation - Align tree toolbar and breadcrumb bar heights to h-10 - Clicking directory name no longer auto-expands (expand only via chevron) - Add max-w-5xl to preview content area - Clean up FileTree: remove unused FileIcon, type field, isFolder checks - Add alphabetical sort to tree children - Remove unused Dialog preview code
|
Failed to generate code suggestions for PR |
There was a problem hiding this comment.
Code Review
This pull request replaces the Go-based AGFS with a Rust implementation (RAGFS), introducing native Python bindings and a new server architecture. It also enhances the vikingbot agent with improved memory retrieval, session management via a new /new command, and refined tool filtering. The LoCoMo benchmark suite and CLI tool received significant updates, including new search constraints and export capabilities. Feedback highlights several critical areas for improvement: hardcoded absolute paths in shell scripts, inefficient memory handling when reading large files, and dangerous recursive deletion logic for S3 root directories. Additionally, the reviewer noted performance bottlenecks caused by blocking I/O in async functions and sequential network requests, as well as potential race conditions in non-atomic database writes and insecure temporary file usage in the build process.
| echo "" | ||
| echo "Step 0b: Deleting data directory /Users/bytedance/.openviking/data..." | ||
| if [ -d "/Users/bytedance/.openviking/data" ]; then | ||
| rm -rf /Users/bytedance/.openviking/data | ||
| echo " ✓ Deleted /Users/bytedance/.openviking/data" | ||
| else | ||
| echo " ✓ Data directory does not exist" | ||
| fi |
There was a problem hiding this comment.
The script contains a hardcoded absolute path /Users/bytedance/.openviking/data. This will cause the script to fail or delete incorrect data on any machine where the user is not bytedance. Use the $HOME environment variable instead.
| echo "" | |
| echo "Step 0b: Deleting data directory /Users/bytedance/.openviking/data..." | |
| if [ -d "/Users/bytedance/.openviking/data" ]; then | |
| rm -rf /Users/bytedance/.openviking/data | |
| echo " ✓ Deleted /Users/bytedance/.openviking/data" | |
| else | |
| echo " ✓ Data directory does not exist" | |
| fi | |
| echo "" | |
| echo "Step 0b: Deleting data directory $HOME/.openviking/data..." | |
| if [ -d "$HOME/.openviking/data" ]; then | |
| rm -rf "$HOME/.openviking/data" | |
| echo " ✓ Deleted $HOME/.openviking/data" | |
| else | |
| echo " ✓ Data directory does not exist" | |
| fi |
| let data = fs::read(&local_path) | ||
| .map_err(|e| Error::plugin(format!("failed to read file: {}", e)))?; | ||
|
|
||
| // Apply offset and size | ||
| let file_size = data.len() as u64; | ||
| let start = offset.min(file_size) as usize; | ||
| let end = if size == 0 { | ||
| data.len() | ||
| } else { | ||
| (offset + size).min(file_size) as usize | ||
| }; |
There was a problem hiding this comment.
The current implementation reads the entire file into memory using fs::read before applying the offset and size constraints. This is extremely inefficient for large files and can lead to Out-Of-Memory (OOM) crashes. Additionally, fs::read is a blocking operation. It is recommended to use tokio::fs::File, seek to the offset, and read only the requested number of bytes.
| // Delete everything under prefix | ||
| self.client.delete_directory("").await?; | ||
| self.dir_cache.invalidate_prefix("/").await; | ||
| self.stat_cache.invalidate_prefix("/").await; |
There was a problem hiding this comment.
| cd crates/ragfs-python && $$MATURIN_CMD build --release --out "$$TMPDIR" 2>&1; \ | ||
| cd ../..; \ | ||
| mkdir -p openviking/lib; \ | ||
| echo "import zipfile, glob, shutil, os, sys" > /tmp/extract_ragfs.py; \ |
There was a problem hiding this comment.
Writing to a fixed filename /tmp/extract_ragfs.py is insecure and can lead to race conditions if multiple builds run on the same machine. Since a unique temporary directory TMPDIR is already created, the script should be placed inside it. Note that you will also need to update the execution and removal lines (120-121) to use the new path.
echo "import zipfile, glob, shutil, os, sys" > "$$TMPDIR/extract_ragfs.py"; \
| })?; | ||
| zip.start_file(name_str, options)?; | ||
| let mut file = File::open(path)?; | ||
| std::io::copy(&mut file, &mut zip)?; |
There was a problem hiding this comment.
| } | ||
|
|
||
| // Write file | ||
| std::fs::write(&final_path, bytes)?; |
There was a problem hiding this comment.
| let backend = self.backend.read().await; | ||
|
|
||
| let exists = backend.path_exists(&normalized)?; | ||
|
|
||
| if exists { | ||
| // Check if it's a directory | ||
| if backend.is_directory(&normalized)? { | ||
| return Err(Error::IsADirectory(normalized)); | ||
| } | ||
|
|
||
| // Update existing file | ||
| backend.update_file(&normalized, data)?; | ||
| } else { | ||
| // Create new file | ||
| if !matches!(flags, WriteFlag::Create) { | ||
| return Err(Error::not_found(&normalized)); | ||
| } | ||
|
|
||
| // Check parent exists | ||
| let parent = backend.parent_path(&normalized); | ||
| if parent != "/" { | ||
| if !backend.is_directory(&parent)? { | ||
| return Err(Error::not_found(&parent)); | ||
| } | ||
| } | ||
|
|
||
| backend.create_file(&normalized, 0o644, data)?; |
There was a problem hiding this comment.
| for idx, memory in enumerate(filtered_memories, start=1): | ||
| uri = getattr(memory, "uri", "") | ||
| abstract = getattr(memory, "abstract", "") | ||
| score = getattr(memory, "score", 0.0) | ||
|
|
||
| # First, try to build full memory with content | ||
| try: | ||
| content = await client.read_content(uri, level="read") | ||
| except Exception: | ||
| content = "" | ||
|
|
||
| if content: | ||
| # Try full version first (no abstract when content is present) | ||
| memory_str = ( | ||
| f'<memory index="{idx}" type="full">\n' | ||
| f" <uri>{uri}</uri>\n" | ||
| f" <score>{score}</score>\n" | ||
| f" <content>{content}</content>\n" | ||
| f"</memory>" | ||
| ) | ||
| else: | ||
| # No content available, use link-only version | ||
| memory_str = ( | ||
| f'<memory index="{idx}" type="link">\n' | ||
| f" <uri>{uri}</uri>\n" | ||
| f" <score>{score}</score>\n" | ||
| f"</memory>" | ||
| ) | ||
| return "\n".join(user_memories) | ||
| return "" | ||
|
|
||
| # Check if adding this memory would exceed the limit | ||
| memory_chars = len(memory_str) | ||
| if user_memories: | ||
| memory_chars += 1 | ||
|
|
||
| if total_chars + memory_chars <= max_chars: | ||
| user_memories.append(memory_str) | ||
| total_chars += memory_chars | ||
| else: | ||
| # If full version is too big, try link-only version | ||
| link_only_str = ( | ||
| f'<memory index="{idx}" type="link">\n' | ||
| f" <uri>{uri}</uri>\n" | ||
| f" <score>{score}</score>\n" | ||
| f"</memory>" | ||
| ) | ||
| link_chars = len(link_only_str) | ||
| if user_memories: | ||
| link_chars += 1 | ||
|
|
||
| if total_chars + link_chars <= max_chars: | ||
| user_memories.append(link_only_str) | ||
| total_chars += link_chars | ||
| else: | ||
| # Even link-only is too big, skip this memory | ||
| continue | ||
|
|
||
| return "\n".join(user_memories) |
There was a problem hiding this comment.
The _parse_viking_memory function performs sequential await client.read_content calls for each memory entry. With the increased limit of 30 memories, this can significantly increase latency. It is recommended to fetch the contents in parallel using asyncio.gather before iterating to build the result string.
| user_results = [] | ||
| if messages_by_sender: | ||
| # 限制最大并发数为 5 | ||
| semaphore = asyncio.Semaphore(5) |
There was a problem hiding this comment.
The semaphore is instantiated inside the execute method, meaning it only limits concurrency within a single hook execution. If multiple requests trigger this hook simultaneously, the total number of concurrent commits to the server will not be restricted. Consider moving the semaphore to the class level or a shared configuration to effectively manage global concurrency.