Replace the derived boolean with an extensible mode so restricted inheritance can become a single additional state.
Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com>
* fix(agent-evolution): preserve legacy experience lineage tags
* fix(tags): relax character restrictions while retaining length limits
* fix(tags): remove search tag format and length restrictions
* fix(tags): retain only comma validation for search tags
* fix(tags): limit relaxation to charset and length checks
* Revert "ci: persist BuildKit mount caches across runs (#4421)" (#4467)
* ci: build the release docker image once (#4469)
* ci: build the release docker image once, not twice
release.yml carries a full copy of the docker build/manifest jobs from
build-docker-image.yml. Both fire on the same release: the tag push triggers
build-docker-image.yml while the release event triggers release.yml's copy, so
every release builds the same image 4 times (2 arch x 2 workflows, ~16 min
each) and both pipelines race to write the same v0.4.x and latest tags.
Drop the copy. build-docker-image.yml has to exist anyway (main images, manual
dispatch) and emits the identical tag set for a tag ref.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01247pby58vheLBLmHPV3WKQ
* ci: assert the release's docker image actually landed
The tag push and the release event fire in the same second, so the image is
still ~16 min from existing when release.yml starts. Poll the tag's
build-docker-image run, fail on a missing or failed run, then inspect both
registries for the version tag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01247pby58vheLBLmHPV3WKQ
* ci: assert latest points at the released tag too
The version tag existing was never the failure mode; latest silently staying
on the previous release was. Compare digests instead of just checking the
version tag is present.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01247pby58vheLBLmHPV3WKQ
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(parser): adapt AnyDoc 0.2 document model (#4509)
Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com>
* fix: watch task status
* fix: watch task create when add
* fix: ut
* fix: watch task create when add
* fix: ut
* feat: connector support user resources
* test: reproduce Feishu OAuth v3 refresh failure
* test: cover legacy Feishu refresh tokens
* fix: support Feishu refresh token formats
* fix: add logs
* fix: ut
* fix: feishu doc watch
* fix: ai review
* fix: ai review
* test(cli): copy an actual file in cp integration test
* fix: add some log
---------
Co-authored-by: Zayn Jarvis <zaynjarvis@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: bot-of-qin-ctx <qin_haojie@qq.com>
Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com>
An exact-match lookup — finding a record by a tag that carries an external
id, say — has no meaningful query text. Callers were forced to invent one,
which made the similarity score noise and left the result at the mercy of
whatever recall the made-up query happened to produce.
find() now accepts an empty query as long as a filter narrows the search.
The result is then fully determined by that filter, so there is nothing to
embed or rank: the request is resolved from the metadata store directly and
`score` stays 0 rather than a fabricated value callers might sort on.
Scoping goes through a new filter_in_tenant, which reuses the very same
_build_scope_filter the vector path uses. Tenant isolation, ACL grants and
target-directory limits therefore stay identical between the two paths — a
hand-built filter here would be one refactor away from silently losing them.
Validation stays at the top of find() so a bad request is still rejected
before any initialization, and an empty query with no filter is still an
error: without either one there is nothing to narrow by, and returning an
arbitrary slice of the store would be worse than failing.
search() is deliberately left alone: it expands intent from session context,
which has no meaning without a query.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(resource): reject a source with no content at ingestion
Adding a zero-byte file currently succeeds and produces an empty
resource. With the Understanding API disabled -- the default, since
ParserApiConfig.enable is False -- nothing on the internal parse path
looks at the size, so the file is staged, parsed, and indexed as an
empty entry. Directory imports already refuse the same input:
directory_scan.py skips any zero-byte member as an "empty file". A
single-file import should not disagree with that.
Check it in UnifiedResourceProcessor.prepare, the one point every
ingestion path passes through: prepare_durable_source freezes a source
there before the request touches the tree, and process() calls it for
anything not frozen earlier. So local files, uploads, remote downloads,
git and Feishu sources are all covered by one check, at the moment the
bytes are first in hand.
InvalidArgumentError maps to 400 through ERROR_CODE_TO_HTTP_STATUS.
Where ingestion runs asynchronously -- a plain remote URL with the
Understanding API off is queued, and the response has already been
sent -- the same error fails the task instead of the request.
Deliberately narrow:
- Only zero bytes. A one-byte file is still accepted; this is not a
minimum-size policy.
- Only regular files. A directory has no meaningful size and is skipped,
so directory and repository imports containing empty files are
unaffected.
- A stat failure is left to the normal ingestion path to report.
- content/write is untouched. Creating an empty file there is an explicit
user action, not an ingestion accident.
The error names the caller's own file rather than the temp working copy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(resource): clean rejected temporary sources
* fix(resource): preserve queued error codes
* test(resource): focus empty-source regression coverage
* ci: drop dedicated empty-resource regression step
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(privacy): serialize config mutations under pathlock and propagate storage errors
- Hold the AGFS tree pathlock around upsert/activate_version/delete read-modify-write
so concurrent updates no longer all read empty meta and write version 1.
- Only NotFoundError/FileNotFoundError map to empty results; storage, permission,
and deserialization errors now propagate instead of being swallowed.
- Regression tests: concurrent upserts keep every version; exists() re-raises
storage errors.
* fix(privacy): wait for config locks and preserve read errors
* fix(session): honor output language for working memory
* fix(session): preserve multiline language detection
---------
Co-authored-by: Yohanes <CryoThrust@users.noreply.github.com>
* refactor(cli): unify tag flag to --tags for add-resource and reindex
Both add-resource and reindex still used the repeatable --tag flag, while
write/ls/tree/grep/glob already use comma-separated --tags. Align them so
every tag-carrying command accepts --tags k=v,k=v consistently.
- add-resource: --tag (repeatable) -> --tags (comma-separated)
- reindex: --tag (repeatable) -> --tags (comma-separated)
- update the two CLI parse tests accordingly
Co-authored-by: TRAE CLI <traecli@bytedance.com>
* feat(tags): constrain search tag charset and length
Explicit k=v search tags previously only required a single '=' and
non-empty sides, so free-form text (spaces, slashes, non-ASCII) and
unbounded length could reach the vector store. Tighten normalize_search_tag
to keep tags as stable identifiers:
- key/value must match ^[a-z0-9][a-z0-9_.-]*$ (after lower-casing)
- key <= 64 chars, value <= 128 chars
- add unit tests for charset and length boundaries
- document the rules in retrieval/content API docs
Co-authored-by: TRAE CLI <traecli@bytedance.com>
---------
Co-authored-by: TRAE CLI <traecli@bytedance.com>
On an invalid ov.conf the server exited 1 with only the raw parse error. Bootstrap now reports the resolved config path and points the operator at 'openviking-server doctor' and examples/ov.conf.example. Also include the resolved path in the local 'server' section type error for consistency. FileNotFoundError output and the exit code (1) are unchanged.
* fix(language): ignore bare import paths during detection
Exclude bare domain paths from language detection so repeated .com imports are not counted as Portuguese stopwords. Preserve the original paths in summarization prompts.
仅在语言检测时排除裸域名路径,避免重复的 .com import 被计为葡萄牙语常用词;摘要 Prompt 仍保留原始路径。
* test(language): cover import path noise in directory overviews
Cover code-only, Chinese, Portuguese, and explicit-override cases while ensuring import paths remain in overview prompts.
覆盖纯代码、中文、葡萄牙语和显式语言配置,并验证目录摘要 Prompt 保留原始 import 路径。
Route batched content writes through the same in-place writer as single-file writes so memory replace operations keep existing hidden fields.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Restore creation (insertion) order for admin list_accounts/list_users,
reverting the always-on lexicographic sorting introduced in #4409. No
sorting parameters are exposed; listing simply preserves the order in
which accounts/users were registered.
Applied across the server manager/router, the Python/Go/TS SDKs, the ov
CLI, and the admin docs/tests.
Co-authored-by: TRAE CLI <traecli@bytedance.com>
* fix(rerank): validate credentials for explicitly configured providers
RerankConfig checked required fields for openai and litellm only. An explicit
`provider: cohere` without api_key, or `provider: vikingdb` without ak/sk, was
accepted at load time. is_available() then returned False and
HierarchicalRetriever fell back to plain vector search, logging a single
info-level line saying rerank was not configured.
The new checks run against the effective provider, matching the existing
openai and litellm branches. Auto-detection is unaffected, since detecting
cohere already requires api_key and detecting vikingdb already requires ak and
sk. An empty RerankConfig() still resolves to no provider and stays valid.
Drops test_default_provider_is_vikingdb, which asserted a default that
auto-detection replaced and had been failing on main. Rewrites
test_unknown_provider_raises_value_error to actually cover an unknown provider
and adds coverage for the two providers that were missing validation.
* docs(configuration): state required credentials per rerank provider
The rerank section described credential inference but not the fields each
provider requires when provider is set explicitly.
---------
Co-authored-by: Terminator666666 <Terminator666666@users.noreply.github.com>
AsyncHTTPClient.create_session() takes (session_id, options); memory_policy
is a key of CreateSessionOptions, not a keyword argument. Three production
call sites still pass it as a keyword and raise
TypeError: AsyncHTTPClient.create_session() got an unexpected keyword
argument 'memory_policy'
on every session that does not already exist:
- openviking/ingest/replay.py, ConversationReplayClient.ensure_session:
`ingest backfill` fails on every new session. The orchestrator catches
per-session exceptions, so a first backfill prints one error per session
and finishes with 0 commits.
- bot/vikingbot/openviking_mount/ov_server.py, VikingClient.ensure_session.
- openviking/session/train/components/session_commit.py,
SessionCommitPolicyTrainer._commit_one, which swallows the TypeError and
returns a failed commit record with an empty task_id.
All three now pass options={"memory_policy": policy}, and options=None when
no policy is configured. benchmark/locomo/vikingbot/import_to_ov.py already
used that form.
The three test fakes accepted the obsolete keyword, so none of the paths had
regression coverage. They now mirror the real SDK signature: reverting any
one of the three fixes fails its tests.
Fixes#4493
* fix: directory understanding_api & fix temp empty file
* fix: understanding_api error处理
* fix: api err msg
* fix: html test
* fix: support directory and HTML imports via UnderstandingAPI
* fix: support directory and HTML imports via UnderstandingAPI
* fix: director max files and zip msg
* fix: director max files and zip msg
* feat: support vector record IDs across filesystem APIs
- centralize deterministic vector record ID generation and migration handling
- allow stat and read to resolve file IDs with actionable missing-index diagnostics
- add selectable filesystem fields and script-friendly ls, tree, and glob output
- preserve complete IDs in simple output and honor tree --simple without fields
- restrict Python SDK ID passthrough to supported read-only endpoints
- preserve explicit empty glob extra_fields for metadata responses
- retain the dedicated Codex OAuth doctor diagnostic path
- document the public API behavior and add CLI, SDK, storage, and server regressions
Co-authored-by: TRAE CLI <traecli@bytedance.com>
* fix(cli): preserve full record IDs in field tables
Render filesystem record IDs without abbreviation in both table and simple field modes so the values can be passed directly to stat and read. Add a regression test for normal table rendering.
Co-authored-by: TRAE CLI <traecli@bytedance.com>
---------
Co-authored-by: Maojia Sheng <shengmaojia@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Group shared uploads into hourly buckets so the upload root never becomes one
huge flat directory (which made cleanup enumerate hundreds of thousands of
entries and take minutes), and rework cleanup to be bounded and best-effort.
Storage layout:
- New uploads: viking://upload/<YYYYMMDDHH>/<uuid>/{content,meta}, UTC hour.
upload_id is <YYYYMMDDHH>-<uuid>; the 10-digit hour prefix is the bucket and a
format marker, and the in-bucket directory is just the uuid (no repeated
prefix). temp_file_id stays shared_<upload_id> (external contract unchanged).
- Legacy flat <13-digit-ms>-<uuid> uploads stay readable; _read_shared_meta
picks exactly one path by id format, no fallback probe.
Cleanup (best-effort, off the request path, oldest-first via name-ascending ls):
- module-level due_at (epoch) + pending throttle so requests don't pile up
duplicate jobs;
- YYYYMMDDHH bucket expires at bucket_start + 3600 + ttl and is removed whole
(rm -r); scan stops at the first live bucket;
- legacy flat uploads expire by created_at + ttl and are removed individually;
- other/malformed dirs are removed only when temp_upload.cleanup_invalid_dirs is
enabled, and legacy flat uploads are never treated as invalid;
- every deletion logs kind/uri/elapsed_ms.
Shared upload writes, failure rollback, and cleanup deletes use
auto_pathlock=False; VikingFS.rm/write_file/write_file_bytes gain an
auto_pathlock parameter (default True, no behavior change for other callers).
Add temp_upload.cleanup_invalid_dirs config flag (default False).
Co-authored-by: TRAE CLI <traecli@bytedance.com>