DSH marks session-backed subagents with a durable origin field, while the OpenViking plugin previously enrolled every session in profile, recall, capture, and teardown commit. Add one opt-in boundary at the plugin hooks so operators can exclude those child sessions without changing existing installations.
Constraint: DSH classifies supported child sessions through SessionHeader.origin=subagent
Rejected: Independent capture and recall filters | teardown commit semantics would require per-state policy and a broader runtime refactor
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not classify from parentSession because human-driven forks may also carry lineage
Tested: Node 22.19 and Node 24 plugin checks; 341-pass memory-plugin matrix; npm pack dry-run
Not-tested: Real DSH CLI to OpenViking server E2E; unclassified timer or cron sessions
Co-authored-by: czyyyy <255856754+kwistzzqq-byte@users.noreply.github.com>
ZCode parses config-file hooks with strict zod rules (matcher:string().min(1),
groups .strict()). Empty-string matchers fail validation and safeParse drops
the ENTIRE user hook source silently — hooks never fire while every component
looks healthy. Omitted matcher matches all events, preserving semantics.
Incident reproduction and bundle-extraction provenance documented in operator
workspace: projects/zcode-ov-dsh-plugin/docs/post-install-verification.md
Co-authored-by: SearXNG Agent <agent@searxng.local>
* feat(memory-plugin): add ov-memory-doctor skill and diagnostics script for Claude Code and Codex
* docs(memory-plugin): link docs and mark the Volcengine-hosted service in the doctor skill
* feat(memory-plugin): add a Server health section to the doctor for local deployments
When the resolved url is loopback the doctor now inspects the server side:
ov.conf startup blockers (plugin-only keys the server rejects, dev mode on a
non-loopback bind, empty root_api_key, port mismatch, relative workspace,
unexpanded $VAR secrets, provider credential rules), the server process and
port owner (pid file, lsof/ss, docker container and its /app/.openviking
mount), the vector index's recorded embedding vs the configured one, the
server log when log.output is a file, and GET /ready. Remote servers get the
/ready probe only. The docker pending_initialization stub is recognised in
the Connection section. Skills, references and READMEs describe the new
section; provider-level validation stays with openviking-server doctor.
* refactor(memory-plugin): trim the doctor's Server health section to the port, plugin-only ov.conf keys and /ready
The section replicated the server's own config validation (top-level and
server.* key allowlists, provider credential rules, vlm, workers) and inspected
the pid file, docker mounts, systemd, the vector collection metadata and the
server log. All of that is what openviking-server reports itself at startup or
what `openviking-server doctor` covers, and the allowlists would drift with
every new config field. Keep what the server cannot tell the client: whether
anything listens on the port, the plugin-only ov.conf keys the server refuses
to start on, and GET /ready.
doctor-core.mjs is now synced only to the plugins that ship a doctor script;
the opencode and zcode copies were never imported.
The async-path test asserted the parent hook exits within a hard-coded
650ms wall-clock budget. On loaded CI runners the node cold start plus
ESM module-graph load alone can exceed that budget (observed 4.8s on a
contended runner), making the test flaky without any real regression.
Drop the wall-clock latency budget and keep the two assertions that
actually pin the async contract:
- completedResponses === 0: every server response is delayed 700ms, so
a parent that exits having completed none provably never awaited the
network. A synchronous fallback completes both before exiting, so the
degenerate path is still caught (verified by forcing maybeDetach to
return false).
- elapsed < OPENVIKING_TIMEOUT_MS: retained as a hang guard only.
Also raise the detached-worker waitFor budget from 5s to 20s: on a slow
runner the worker needs cold start + two 700ms-delayed responses + state
writes, which can exceed the old 5s default.
Co-authored-by: mac <bishopapril850965@yahoo.com>
findVikingUri() checked the path-like keys and then swept every remaining
argument value, so a local write or edit whose CONTENT merely mentioned a
viking URI was denied and no file was created:
write { file_path: "/home/me/notes.md",
content: "docs say viking://user/default/ is virtual" } -> deny
The sweep still runs — it is what catches an unusual or nested path key — but
it now skips arguments that carry content rather than a location
(content, new_string, old_string, file_text, ...). A URI in file_path, path,
uri, an unknown nested path key, or a bash command still denies.
Vendored copies regenerated with examples/memory-plugin-shared/sync.mjs.
Use a stable Node command for the DSH stdio MCP proxy so Electron desktop hosts do not try to spawn their own app binary as the proxy runtime.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
* fix(plugin): honor explicit recall context timeout
Let operator-configured recallContextTimeoutMs apply even when context recall skips rewrite and query expansion, so low-latency configs can still extend the request deadline explicitly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(plugin): sync recall timeout override
Keep the explicit recall context timeout behavior in the shared plugin source so generated plugin copies stay synchronized.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#4058): [Bug]: Codex memory plugin replays historical turns after resume or transcript compaction
Fixes#4058
Ref: https://github.com/volcengine/OpenViking/issues/4058
* fix(codex): retire committed cursors and keep activity-based concurrency
Preserving the transcript cursor after a commit stops the replay, but it also
means nothing deletes state files any more: clearState() lost its last caller,
so every codex session — including ones that never captured a turn — leaves a
file behind, and listStates() reads all of them on every SessionStart.
The sweep now retires cursor-only states in the same pass: a real cursor is
kept for resume until OPENVIKING_CODEX_COMMITTED_TTL_MS (default 30 days, past
the life of the codex rollout it indexes), and a state that never captured
anything goes on the idle schedule, which is what the old sweep did with it.
Releasing ovSessionId also wrote lastUpdatedAt, making a committed session look
freshly active; saveState() takes touch:false so the field keeps meaning "last
transcript activity" for both the active window and retention.
Requiring a live ovSessionId to count as recently-active made the heuristic
miss sessions PreCompact had just committed, which can still be running: the
count is back on activity alone, and only a state with a live session is
committed.
Also name the shrink predicate: role === "user" covers tool results too
(normalizeCaptureRole maps them onto the user role), so findLastHumanTurnIndex
requires a text part, and the no-human-turn fallback to a full replay is now
visible in the log instead of silent.
---------
Co-authored-by: 7487 <1042653432@qq.com>
updateStatus() called ctx.ui.setStatus(status) with a single argument,
but the pi extension API signature is setStatus(key, text). With a
single argument the status string becomes the key and the text is
undefined, which clears the status entry instead of setting it -- so
the OpenViking status line never shows on pi 0.84.x, silently.
Pass "openviking" as the key so the status text actually renders.
Co-authored-by: veryvideo <veryvideo@users.noreply.github.com>
* docs: add anydoc office converter design
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(parse): add AnydocConfig and anydoc adapter skeleton
Register anydoc parser config with firecrawl-anydoc dependency and a small
attribute adapter for binding name normalization ahead of converter wiring.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(parse): add anydoc conversion core
Serialize anydoc documents to GFM while preserving embedded images through the existing storage media pipeline.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(parse): address Task 2 review findings
Move inline-code backslash escaping out of the f-string expression for
Python 3.10/3.11 compatibility, and stop tracking the SDD task report
in the product tree.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(parse): wire Word and legacy Doc to anydoc
Route Word and real OLE documents through the shared converter while preserving configurable legacy fallbacks and OOXML disguise handling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(parse): honor anydoc config and safe fallbacks
Wire application parser settings into the default registry and prevent unsupported ODT/RTF files from reaching python-docx.
* feat(parse): wire PowerPoint and EPUB to anydoc
Route supported presentation and EPUB formats through the shared converter while preserving safe format-specific legacy fallbacks.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(parse): wire Excel parser to anydoc
Route modern spreadsheet formats through anydoc with safe row truncation while preserving legacy process-pool conversion when anydoc is disabled.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(parse): preserve Excel ingestion options
Forward directory parse settings through the anydoc path and make skipped row truncation observable.
* docs(parse): document anydoc Office support
Reflect the expanded Office and EPUB format coverage while keeping PDF behavior explicitly unchanged.
* fix(parse): address final anydoc review findings
Resolve signatureless CSV conversion and preserve ingestion options across Office parsers while documenting and testing XLSB behavior.
* feat: unify office parsing with anydoc
* refactor: simplify anydoc renderer organization
* fix(parse): align anydoc parser config switch
* fix(anydoc): preserve legacy parser compatibility
* fix(anydoc): restore legacy safeguards
* refactor(anydoc): keep Office parsing on the unified path
* fix(anydoc): preserve config and benchmark compatibility
* fix(markdown): isolate link rewrite state per parse
---------
Co-authored-by: 张剑锋 <zhangjianfeng@ydjdev.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com>
* feat(mcp): return images as native content blocks
* test(mcp): allow configured tool decorators
* feat(mcp): return audio as native content blocks
* feat(mcp): add embedded-resource download mode
* fix(mcp): bound native media reads
* fix(mcp): add actionable media download fallback
* fix(mcp): point directory reads at list, share URI suffix parsing
- directory hint now names the list tool / `ov ls` / `ov tree`
- audio MIME sniffing ignores query/fragment like the extension gate
does, so `clip.ogg?v=2` no longer fails as an unsupported format
- bound the preflight stat fan-out with the existing read semaphore
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXgAtkdLQZhXu4ZbHpaDqR
* fix(mcp): validate video reads before fallback
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Allow openai-codex VLM configuration to carry reasoning_effort through credential normalization and into Responses API requests so deployments can tune model effort without out-of-tree patches.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(sdk): sync go/ts/python SDKs with server find/search, recall, and admin changes
Server-side changes recently landed that the language SDKs had drifted from:
- find/search results now return `tags` and no longer return
`category`/`match_reason`/`relations`/`overview` (#3730). Go's strict
struct was the only one broken; update MatchedContext accordingly.
- new admin endpoints for agent-evolution and per-account settings (#3695).
- public `search/recall` endpoint was missing from all SDKs.
Changes:
- python: add `level`/`since`/`until`/`time_field` to find/search; add an
`extra` escape hatch to find/search/add_resource/write/batch_write so new
server fields can be passed without an SDK bump (only forwarded when set,
preserving `level=0`); add `recall` and the four admin methods.
- go: fix MatchedContext (add Tags, drop removed fields), add Recall and the
four admin methods.
- typescript: type MatchedContext/FindResult, add RecallOptions, add `recall`
and the four admin methods.
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
feat(sdk): unify options APIs and sync latest server interfaces
- migrate complex Python SDK calls to typed options dictionaries
- add dedicated context search and consistent extra-field handling
- align Go and TypeScript options with omission-aware serialization
- support session config, event tags, Agent Evolution date filters,
OpenViking Assets, batch write, downloads, and create_parent
- refresh SDK tests and examples across all three languages
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): address options API review findings
- fix Go session extra merging and Python message precedence
- adapt LangChain calls to the Python options API
- migrate repository examples, tests, and documentation
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): complete options migration and message parity
- migrate remaining Python SDK benchmarks to options dictionaries
- normalize empty parts consistently for single and batch messages
- add regression guards for repository SDK call sites
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): align reindex options after main rebase
- preserve reindex tags in Python typed options
- add reindex extra support for Go and TypeScript
- reject official fields passed through extra across SDKs
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
feat(sdk): support legacy keyword options
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
docs(sdk): use explicit Python SDK arguments
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): support set tags extra options
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): expose Go add resource options
Expose AddType and ProcessingMode through Go AddResourceOptions and serialize them to the resources API. Add a regression test covering the resulting request payload.
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
feat(sdk): flatten core Python client options
Co-authored-by: TRAE CLI <traecli@bytedance.com>
docs(sdk): align Python examples with flattened options
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): preserve core API compatibility
Co-authored-by: TRAE CLI <traecli@bytedance.com>
refactor(python-sdk): move resource hints to options
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): align resource option callers
Co-authored-by: TRAE CLI <traecli@bytedance.com>
test(sdk): cover recursive reindex forwarding
Co-authored-by: TRAE CLI <traecli@bytedance.com>
fix(sdk): preserve Go options compatibility
Co-authored-by: TRAE CLI <traecli@bytedance.com>
feat(python-sdk): expose message peer id
Co-authored-by: TRAE CLI <traecli@bytedance.com>
test(python-sdk): consolidate options coverage
Co-authored-by: TRAE CLI <traecli@bytedance.com>
feat(python-sdk): add parts and flatten image search
Co-authored-by: TRAE CLI <traecli@bytedance.com>
* docs(sdk): align Python call examples
Co-authored-by: TRAE CLI <traecli@bytedance.com>
---------
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: Qin Haojie <qinhaojie.exe@bytedance.com>
Reapply previously injected recall blocks to historical user messages from a per-session, atomically persisted ledger. This preserves byte-stable request prefixes across turns while still performing fresh recall for the newest prompt.\n\nFixes #4137
* fix(ov_dream): accept a message whose content is a plain string (#4221)
parse_messages() assumed every OpenClaw message body was a block list.
Iterating a plain string yields characters, so `"p".get("type")` raised
AttributeError out of the parser — before any session was committed, which
made one such message disable the skill for the whole workspace rather than
skip that message. The reporter measured 91 string bodies in 1609 real
messages.
Flatten the body through a helper that treats a string as text and skips
blocks that are not dicts.
* test(ov_dream): consolidate string content coverage
---------
Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com>
* feat(uri)!: reject uid-less current-user shorthand in favor of viking://~
viking://user/<segment> (memories/resources/skills/peers/privacy/sessions
without a user id) was ambiguous with a user literally named after the
segment, and a user actually named e.g. "memories" was unreachable for
USER/ADMIN callers. Now that the viking://~ home alias (#4167) covers the
same need unambiguously, the shorthand fails closed at the request
boundary instead of expanding:
- resolve_current_user_uri raises NamespaceShapeError with a corrective
hint naming both viking://~/<rest> and the explicit-uid form. Silently
parsing the reserved segment as a peer user id would misdirect reads
and writes, so rejection is the only safe removal.
- Bare viking://user falls through to the canonical parser and keeps
container semantics (a user key listing it sees only its own space).
- The self-id escape stays: a caller whose user_id equals a reserved
name keeps viking://user/<own-id> as their canonical root. ROOT-role
literal parsing and the legacy viking://session alias are unchanged.
- AddTargetsConfig normalizes stored legacy config spellings
(viking://user/resources|skills) to the viking://~ form at validation
so existing ov.conf/user_config deployments keep working; the accepted
per-user spelling is now viking://~/resources and viking://~/skills.
- usage_reporter keeps canonicalizing the historical shorthand found in
old transcripts and additionally recognizes viking://~/memories/.
BREAKING CHANGE: requests using the uid-less viking://user/<segment>
spelling now fail with 400; use viking://~/<segment> or an explicit
viking://user/{user_id}/<segment> URI.
* refactor(clients): migrate first-party emitters to the viking://~ home alias
Every in-repo client that emitted the removed uid-less current-user
shorthand now sends viking://~/... instead: vikingbot fallbacks and
default sentinels, the LangChain store/tools defaults, the shared
recall-core.mjs (all synced plugin copies), the codex/claude-code/
openclaw/openwebui/dsh/zcode/pi plugin emitters, quick-app examples,
Go SDK example, tau2 benchmark targets, and the eval golden dataset.
Compat kept where legacy strings live in stored user configs: bot and
ov_dream sentinels accept both spellings while emitting only ~, and
recall-core still rewrites legacy viking://user/<reserved> config values
client-side. langchain_openviking._uri now classifies viking://~ with
the explicit-user shape so canonicalized server responses keep matching
a ~ root. Plugin READMEs note the server requirement for the alias.
* docs: replace current-user shorthand guidance with the viking://~ home alias
Rewrite every EN/ZH doc and model-facing prompt that advertised the
uid-less viking://user/<segment> spelling: URI concept catalogue,
context-types/storage/extraction/retrieval/session/privacy concepts,
configuration guide (with the legacy add_targets auto-normalization
note), resources/skills/sessions/retrieval/admin API references, FAQ,
capability reference, and the openviking-memory / ov-experience-memory /
openclaw / ov-resources skills. The stale MCP viking://user/<path>
dialect passage in the MCP guide is replaced by ~ guidance, and bare
viking://user is documented as the container of user spaces.
* test(api): migrate live API session-used tests off the removed shorthand
tests/api_test/sessions sent uid-less viking://user/skills/... URIs to
record_used, which the request boundary now rejects with 400 (caught by
the API & CLI Integration Tests CI job; these tests need a live server
and are not part of the local suites). The api_test client authenticates
as an admin-role user key, so the viking://~ home alias expands for it.
tests/api_test/common/test_edge_cases.py is left as is: it asserts a 400
for a non-resource add target, which still holds.
* feat(dsh): serve tools over the shared stdio MCP proxy
Replace the dsh bundle's seven hand-registered `viking_*` tools with the
OpenViking MCP surface, reached through the same stdio proxy every other
memory integration starts, and collapse the four duplicated proxy
entrypoints onto a shared config builder.
The bundle now mounts `@deepseek-ai/dsh-mcp-client` (which ships with dsh
itself) on `servers/mcp-proxy.mjs`. Pointing an MCP SDK client straight at
the server's `/mcp` endpoint does not work: with `stateless_http=True` the
server still answers `GET /mcp` with an idle 200 SSE stream, and once the
SDK client opens that standalone stream it stops resolving POST responses,
so `tools/list` never returns. The stdio proxy owns the transport itself
and is unaffected.
`trimSlash`, `normalizePath`, `uniq`, the watched-credential-path list and
the cfg -> proxyConfig mapping existed in four near-identical copies
(claude-code, codex, opencode, agent-plugins; the last one carried a
"keep in sync with claude-code" comment). They move to
`memory-plugin-shared/lib/mcp-proxy-config.mjs` and all five entrypoints —
including the new dsh one — now shape their config through
`buildMcpProxyConfig`. Behavior is preserved per field, including codex's
explicit `mcpUrl` override, claude-code's `ovcli.conf` credential-source
probe, and opencode's extra watched config file.
The bridge is mounted last in `apply()` so a proxy that fails to start
cannot hold up profile injection, recall, capture, commit, or the URI
guard registrations above it.
* feat(dsh): add to the unified installer and ship the shared skill
The bundle now registers its own isolated `ctx.skills` provider serving the
shared `openviking-memory` skill, so DSH gets the same guidance the Claude
Code, Codex, and Cursor integrations ship. `sync.mjs` distributes the skill
to the bundle, and the provider uses `includeDefaultRoots: false` so it
never shadows DSH's own project/user skill catalog.
`install.sh` grows a `dsh` harness id, auto-detected like the others, plus a
profile prompt that defaults to `web` (`--dsh-profile` / `OPENVIKING_DSH_PROFILE`
answer it up front). The installer always installs the published package:
`dsh plugin` forwards to pnpm, and a linked source tree cannot resolve the
dsh peers the bundle imports because Node resolves them from the checkout's
realpath rather than from the profile.
Documentation is restructured around installing rather than internals. The
integration page now leads with the one-line installer and keeps behavior at
the level the other harness pages use, with configuration in a details block;
design rationale moves to the bundle README, which itself leads with Install
and groups the rationale under "Design notes". Capability-reference claims
that dsh is outside the unified installer are corrected.
* chore(dsh): release 0.2.0
The MCP tool surface, the stdio proxy transport, and the bundled skill all
change what the bundle does for an existing user, so this is a minor bump
rather than a patch. 0.1.0 remains the native-`viking_*` tool surface.
* docs(dsh): note pnpm's 24h minimum release age
pnpm 11 refuses releases younger than minimumReleaseAge (24 hours by
default), and surfaces it as a registry 404, so installing a freshly
published version reads as "the package does not exist".
* fix(dsh): honour dev source mode in the installer
install_dsh ignored SOURCE_MODE and always fetched the published package,
so selecting "current checkout" installed npm's build instead of the
working tree and validation still reported success.
npm is the bundle's only distribution channel, so the github/tos choice
does not apply to it: every mode except dev now installs the published
package, and dev packs the checkout with npm pack first. It has to arrive
as a real package rather than a link, because a linked source tree
resolves its dsh peers from its own realpath and misses the profile's
hoisted node_modules. The install line reports which source was used.
* fix(dsh): make repeated installs actually overwrite
Two ways a re-run silently kept stale code:
pnpm treats an already-satisfied version as a no-op regardless of which
tarball the file: dependency points at, so a dev re-install after editing
the checkout left the previous build in place. Local installs now drop the
package before adding it back; that is confined to local sources, since
doing it for the registry path would leave nothing installed when add
fails.
A bare package name has the same effect in reverse: a profile holding a
dev build satisfies it, so switching back to the published package was a
no-op. The registry path now asks for @latest.
The packed tarball is named after a fingerprint of the checkout's shipped
files, so an unchanged checkout skips the pack and keeps a stable path in
the profile lockfile.
- VolcEngine selection now opens an access-tier submenu: Agent Plan
(api/plan/v3), Coding Plan (api/coding/v3), or pay-as-you-go API, with
doubao-seed-2.0-lite / doubao-embedding-vision defaults for the plans
- BytePlus gains the same submenu with ModelArk Coding Plan
(api/coding/v3, dola-seed-2.0-lite / skylark-embedding-vision) and
pay-as-you-go
- Embedding 'Other (manual)' becomes a Custom/manual submenu offering
interactive OpenAI-compatible prompts (URL, key, model, dimension)
alongside the editor hand-off
- Custom VLM/embedding prompts accept !back to return to the provider
menu; plan submenus support back navigation and seed defaults from the
existing config endpoint (unknown volces.com/bytepluses.com endpoints
are offered as a keep-current option)
- Two-step flow seeds the VLM plan menu from the embedding tier choice;
summaries now show the api_base so tiers are distinguishable
- Fix provider re-detection for BytePlus plan endpoints (domain-based,
since VolcEngine and BytePlus share the volcengine provider string)
- Document the plan endpoints in ov.conf.example
* docs: fix integration docs and comments that contradict the code
- codex: credential resolution in the default `auto` mode is env-first — `credentials.mjs`
only falls back to `ovcli.conf` when no credential env var is set, while the docs and the
`config.mjs` header comment claimed `ovcli.conf` wins by default. Also document
`OPENVIKING_CREDENTIAL_SOURCE=cli`, which was undocumented.
- codex: the four hook scripts send the key as `X-API-Key` in addition to
`Authorization: Bearer`; the README documented Bearer only.
- claude-code: the OV session id is `cc-<cc_session_id>` verbatim (`deriveHarnessSessionId`
does no hashing), not `cc-<sha256(cc_session_id)>`.
- claude-code: `hooks.json` registers 9 hooks, not 7 — the responsibilities table was
missing the `PreToolUse` `viking://` guard and the `PostToolUse` skill-experience hook.
- claude-code: archival is triggered client-side (the `Stop` hook commits once
server-reported pending tokens cross `commitTokenThreshold`, default 20000, plus
unconditional commits from `PreCompact` / `SessionEnd` / `SubagentStop`). The README
attributed it to a server-side `auto_commit_threshold`, but
`memory.session_auto_commit.default_enabled` is false and no plugin sends a policy.
- trae / opencode: the MCP proxy transparently exposes the full server tool set (16 tools);
the docs listed a 4-item sample or 11-13 tools and omitted `tree` / `write` / `edit`.
- trae-cli: the installer registers the MCP server as `openviking-memory`, but the verify
step told users to look for `openviking`.
- pi: the manual install block omitted the `pi install <dest>` registration step that the
one-click installer runs, so a hand-copied extension is never registered.
- install.sh: `--uninstall` handles cursor, trae, trae-cn, trae-cli and zcode; the `--help`
text still said Cursor/TRAE only.
- mcp_endpoint.py: the module docstring enumerated 13 tools and omitted `recall`,
`list_watches` and `cancel_watch`; replaced the stale enumeration with a pointer to the
`@mcp.tool` registrations.
* docs: add a cross-integration capability reference page
The agent-integrations section had per-integration install guides but no place
to compare integrations against each other. This adds one bilingual page that
does that, and wires it into the existing pages in both directions.
- New page `docs/{en,zh}/agent-integrations/16-capability-reference.md`: a
dimension-first comparison of every OpenViking integration — active tool
surface, automatic hook surface, install/credential/config layering, recall
and injection, session and commit lifecycle (including a shutdown-path x
harness end-state matrix), compaction takeover, write/delete boundaries,
degradation, and a per-harness profile card for each integration.
- Sidebar: `StructuredSidebarCopy` gains an optional `topItems` field so a
section can list flat entries next to its overview; agent-integrations uses
it to place the new page beside the overview. Other sections are unaffected.
- Links both ways: the overview and all 14 per-integration pages link to the
reference, and the reference links back to each integration page from its
profile card, from the non-coding integration table, and from the custom
agent integration paths. Section cross-references (§x.x) are real in-page
anchor links, generated from the built heading ids.
- trae-cli is documented as TraeCode CLI 2.0 only, installed through a codex
plugin alias; 1.0 and its standalone plugin are called out as unsupported.
- The MCP tool surface is described as 15 tools throughout, matching the
removal of the `recall` tool in favour of `search` with `mode="context"`.
Pages outside this change that still mention an MCP `recall` tool
(04-codex, 12-cursor, 15-agent-plugins, guides/06-mcp-integration) need a
follow-up sweep once that removal lands.
* docs: 更新服务端 MCP 工具面描述,简化信息并明确更新方式
* fix(plugin): report server recall top score
Derive the status snapshot's top score from server-assembled context so /ov no longer reports 0.00 for scored recalls.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
* test(plugin): remove redundant recall state tests
* fix(plugin): remove misleading recall top score
---------
Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com>
Ship the generic openviking-memory SKILL.md from examples/skills as the
canonical source and vendor it into the codex, claude-code, and cursor
memory plugins through the existing shared-file sync script.
- examples/skills/openviking-memory/SKILL.md is the single source of truth
- sync.mjs copies skills verbatim (no GENERATED banner: it would sit ahead
of the YAML frontmatter and break every skill loader)
- sync.test.mjs asserts the vendored copies stay byte-identical
- the marketplace staging script now requires the two newly vendored copies
Split out of #3866: this carries only the generic skill packaging. The
Experience / agent-evolution half of that PR (ov-experience-memory skill,
server MCP experience tools, usage attribution) is deliberately excluded.
* feat: add OpenViking memory integration for TRAE CLI
Add TRAE CLI lifecycle hooks and MCP proxy support, wire the integration into the shared installer, and cover idempotent install and uninstall behavior.
Co-authored-by: TRAE CLI <noreply@bytedance.com>
* fix(trae-cli): cover archive installs and hook payload aliases
* fix: keep TRAE CLI installation explicit
Leave TRAE Desktop detection unchanged and avoid auto-selecting TRAE CLI. TRAE CLI remains available through an explicit harness selection or --harness trae-cli.
Co-authored-by: TRAE CLI <noreply@bytedance.com>
* fix(trae-cli): auto-select installed CLI commands
Detect traecli and traex only when they are available in PATH, then mark and select the TRAE CLI harness automatically.
---------
Co-authored-by: “bianhaonan” <“bianhaonan@bytedance.com”>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
* feat(pdf): refactor MinerU parsing to the official file_parse API
* feat(pdf): remove mineru_api_key from configuration and examples
* feat(pdf): preflight MinerU /health during service initialization
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(plugins): add OpenViking memory for DSH
* feat(dsh-plugin): graft review items — source whitelist, dsh constructors, live recall gate
Applies the #task-65 review verdict's graft list from #3991 onto the
#3993 base:
- capture whitelist: drop every plugin-sourced user message (any plugin,
not just this one) so injected context never mirrors into memory as
human input; recall queries keep their existing scope
- pre-step: register with prepend so this listener sees the final
claimed batch, and short-circuit on signal.aborted around each await
- adopt dsh constructors behind exact-pinned peers (devDependencies
mirror the pins): tools flow through @deepseek-ai/dsh-tools defineTool
(declarative parameters, output schema/render, presentCall per tool),
plugin messages through @deepseek-ai/dsh-llm createUserMessage; a
registration-shape test makes a future rc pin bump fail CI instead of
a user install when the ToolDefinition contract moves
- live-recall.test.mjs: opt-in (OPENVIKING_E2E=1) real-backend gate —
store a sentinel via session commit, wait for extraction, assert
recall returns it; passed against a live OpenViking server in 124s
(note: commit with the default keep_recent_count=10 extracts nothing
from short sessions — the test pins keepRecentCount 0)
- README: why injection is pre-step user messages, not the system
prompt (complete:true personas silently drop prompt assembly), plus
peer-pin rationale and a Testing section
Tests: 15 pass + 1 env-gated (node --test), requires npm ci for the
pinned dsh devDependencies — CI step lands separately (workflow scope).
* ci(pr): install DSH plugin deps before running memory plugin tests
* fix(dsh-plugin): finalize neutral plugin integration
Remove product-specific identifiers from the DSH plugin surface and harden its lifecycle, HTTP contracts, archive tooling, and ordered offline delivery.
Co-authored-by: TRAE CLI <noreply@bytedance.com>
---------
Co-authored-by: Zayn Jarvis <zaynjarvis@gmail.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
* feat(plugins): add Agent Plugins 1.0 portable package
Add agent-plugins/, an Agent Plugins 1.0 conformant package
(https://agent-plugins.org/specification) that any conforming client can
load: plugin.json manifest, an openviking-memory skill teaching the
hook-less recall + persist loop, and an mcp.json stdio entry running a
stdio -> streamable-HTTP proxy that resolves credentials from
OPENVIKING_* env -> ~/.openviking/ovcli.conf -> ~/.openviking/ov.conf,
same as the ov CLI.
servers/shared/* are generated copies of memory-plugin-shared/lib, wired
into sync.mjs / sync.test.mjs TARGETS so they cannot drift silently.
config.mjs / debug-log.mjs / mcp-proxy.mjs are adapted from
claude-code-memory-plugin with the hook-tuning knobs dropped.
plugin.test.mjs validates spec conformance (schema URLs and matching
spec versions, name rules, closed manifest root, semver, skill
frontmatter, referenced files staying inside the plugin root, node
--check on all .mjs) and runs in CI via pr.yml.
The skill treats tree/write/edit as optional, since they only exist on
servers that carry #3936.
Docs: docs/{en,zh}/agent-integrations/15-agent-plugins.md, registered in
the VitePress sidebar and the integration overview tables, plus a link
from the three root READMEs. The docs recommend the per-client plugin
whenever the harness has hooks, with the shared installer one-liner.
Based on #3994 by @ZaynJarvis.
Co-Authored-By: Zayn Jarvis <zaynjarvis@gmail.com>
* docs(agent-plugins): pluralize README title
---------
Co-authored-by: Zayn Jarvis <zaynjarvis@gmail.com>
Preserve result.trace_id across plugin HTTP wrappers, include it in commit success and failure logs, and surface it in user-visible commit confirmations where supported.
* 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>
Coding-agent plugins capped a tool part's `tool_output` at 2000 chars before
POSTing it to `/api/v1/sessions/{id}/messages`. That cap sits below the server's
own externalization threshold (`tool_output_externalization.threshold_chars`,
default 20000), so output in the 2k-20k band was destroyed for no reason and
anything larger never reached `ToolResultStore` - leaving `tool_output_ref`
permanently empty and the `/tool-results` read-back path unusable.
Raise the `captureToolMaxChars` default to 1000000 (a guard against pathological
payloads, not a truncation policy) and lift the opencode/pi clamps that would
otherwise pin it back to 20000. claude-code had no knob at all - two hardcoded
`TOOL_OUTPUT_PART_MAX_CHARS = 2000` constants - so it gains the same config
entry and both capture scripts now read it.
Also stop pi from sending tool output twice: for a tool-only payload the
rawText-derived text part re-rendered the same output the tool part carries.