Commit Graph
53 Commits
Author SHA1 Message Date
t0saki a83b81715b feat(uri)!: remove uid-less current-user shorthand in favor of viking://~ (#4196)
* 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.
2026-08-21 19:00:19 +08:00
t0saki c7044075ef feat(dsh): serve tools over the shared stdio MCP proxy (#4157)
* 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.
2026-08-20 19:05:18 +08:00
zgyandqin-ctx 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>
2026-08-19 17:54:29 +08:00
t0saki 458e7fae37 docs: add a cross-integration capability reference page (#4076)
* 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 工具面描述,简化信息并明确更新方式
2026-08-18 20:38:06 +08:00
t0saki eb5aaf78e9 feat(mcp): consolidate recall into context search (#4075) 2026-08-18 12:45:45 +08:00
Yu ZhangClaude Sonnet 4.6 noreply@anthropic.comqin-ctx
e2a604167d fix(plugin): remove misleading recall top score (#4053)
* 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>
2026-08-18 12:07:59 +08:00
t0saki ff415b905d feat(plugins): package the openviking-memory skill into coding agent plugins (#3974)
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.
2026-08-17 18:47:52 +08:00
t0saki 33043cb1b8 feat(plugins): expose session commit trace IDs (#3977)
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.
2026-08-13 23:29:41 +08:00
agent 2d5aa9cd3d fix(plugins): ship Experience skill in plugin packages (#3946)
* fix(plugins): ship experience memory skill

* fix(openclaw): drop unpublished selector aliases
2026-08-12 11:18:22 +08:00
agent 00f3738edb feat(usage): emit resource-scoped experience usage records (#3921)
* feat(usage): expand experience tracking and log schema

* fix(usage): preserve experience count event names

* refactor(agent-evolution): use generic OpenViking tools

* fix(usage): capture generic OpenViking tool events

* feat(skills): guide cross-agent experience retrieval

* fix(usage): address generic tool migration review
2026-08-11 22:20:18 +08:00
t0saki 7e26fab61c fix(memory-plugins): report tool output verbatim, let the server externalize (#3933)
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.
2026-08-11 19:17:54 +08:00
t0sakiandTRAE CLI fb82168659 fix(claude-plugin): count recalled memory URIs (#3821)
Derive the statusline recall count from unique viking:// references in the injected context and bump the plugin to 0.4.4.

Co-authored-by: TRAE CLI <noreply@bytedance.com>
2026-08-06 14:55:25 +08:00
t0sakiandTRAE CLI 8e98a3c744 fix(memory-plugin): preserve commit payload on retry (#3822)
Keep retention options when retryable Claude session commits are queued and replayed. Add coverage for retryable storage conflicts.

Co-authored-by: TRAE CLI <noreply@bytedance.com>
2026-08-06 14:53:58 +08:00
t0sakiandTRAE CLI 0ab48f96fc fix(session): recover partial capture sessions (#3820)
Treat messages.jsonl as the materialization boundary for session-aware recall,
repair partial session roots during the existing authoritative append path,
and preserve Claude capture cursors when writes never reach the server.
Also replay explicitly retryable storage conflicts across memory plugins.

Co-authored-by: TRAE CLI <noreply@bytedance.com>
2026-08-06 14:48:42 +08:00
t0saki 674f5e6039 fix(retrieval): honor context tier ceilings and stop cooling unserved recalls (#3746)
* fix(retrieval): honor tier ceilings and stop cooling unserved recalls

Follow-up to #3534, from its post-merge review round.

- The abstract-to-overview substitute now applies only to categories whose
  stored abstract is the whole file body. A resource or skill whose abstract is
  missing (`processing_mode=vectors_only`) or over the per-entry cap read its
  body and returned an overview instead, which for a short file is the body
  almost verbatim — crossing the opt-in deepening boundary those categories are
  documented to have, and doing it even under an explicit `detail="abstract"`.
  They now degrade to a bare URI and their body is never read.
- A digest reporting `no_relevant` blanks `rendered`, so the client injects
  nothing, yet those URIs still entered the dedup ledger and were cooled for
  `dedup_turns` turns. That contradicted the ledger's own bare-URI grace rule
  and held memories back from the later turn they were relevant to.
- Flat retrieval reaches built-in memory types outside the four named ones
  (`cases`, `patterns`, `tools`, `trajectories`, skill-usage memories) and
  reported them as an undeclared `memories` category that no tier or penalty
  table covered, so other-peer hits skipped the score penalty and callers could
  not pin their tier. The catch-all is now a declared category with both; it
  stays out of `quotas`, whose buckets it would overlap. Skill-usage memories
  also stop being misread as the `skills` category.
- ZCode, OpenCode and pi own an OV session id but did not forward it, so their
  recalls silently ran without query expansion or cross-turn dedup.
- The context-request deadline covered only the server's 30s rewrite fuse, but
  the pipeline is serial: expansion, retrieval and budgeting all precede it.
  45s covers both fuses and the work between them.
- `plugin` config scope and the `/recall` successor example now match what the
  code actually does.

* fix(retrieval): make the context deadline and expansion opt-out reachable

Forwarding a session id turns on server-side query expansion, an LLM call with
its own 5s fuse, but neither the deadline that was supposed to cover it nor the
switch that turns it off reached the two harnesses this PR newly enabled it for.

- `contextRequestTimeoutMs()` now derives the deadline from the request body
  rather than from `cfg` plus a rewrite flag. The body is what states which
  server stages will run: a session takes the expansion fuse, `rewrite` takes
  the digest fuse, and a bare retrieval takes neither and keeps the caller's own
  budget. Reading `cfg` alone could not tell those apart.
- OpenCode pinned `timeoutMs: 5000` after spreading the helper's options and pi
  ignored them entirely, so the helper's deadline was dead code in both. Their
  own budgets are now defaults rather than ceilings. OpenCode's 5s in particular
  was shorter than the expansion fuse it had just enabled, so a legal request
  would have been aborted client-side and dropped back to the path with neither
  dedup nor expansion.
- OpenCode and pi read `OPENVIKING_RECALL_QUERY_EXPANSION` (and
  `recallQueryExpansion` in their own config files) and set the `configured`
  flag the shared body builder requires, so the documented opt-out exists where
  the cost was introduced.
- The integration overview no longer implies every harness reads the same
  environment knobs, and describes the deadline as per-stage rather than
  rewrite-only.
2026-08-05 23:52:15 +08:00
2cc96e393e feat(retrieval): assemble auto-recall context server-side via /search mode="context" (#3534)
* feat(retrieval): assemble auto-recall context server-side via /search mode="context"

Auto-recall assembly lived in every harness plugin: each one searched per
memory type, read hits back one by one, and stitched a context block with its
own budget and degradation rules. The implementations drifted, and the shared
weaknesses showed up in production injections — roughly half of the entries
degraded to a bare URI plus a score, character budgets distorted up to 6x on
CJK text, and adjacent turns re-injected the same memories.

This moves assembly into the server as one round trip. /find stays an unchanged
stateless primitive. /search gains mode="context" (mode="list" is the default
and byte-identical to before), and /recall becomes a thin preset over the same
kernel with its v1 field names folded onto the new contract.

New assembly kernel under openviking/retrieve/context_assembler/:

- Token budgeting with a CJK-aware estimate replaces the character budget.
- detail="auto" fills breadth-first then deepens: every candidate gets a
  readable floor, then overview, then full for high-scoring entries. An
  oversized tier falls back to the previous one instead of being truncated,
  bounded by max_tokens / candidates * 2 per entry.
- Overview extraction dispatches by source: memory files use their leading
  Summary section, code files reuse code_outline signatures, long documents use
  a heading tree plus first paragraph.
- Directory hits start at overview and read their .overview.md sidecar, since
  directories carry no stored abstract; their full tier stays capped at
  overview. v1 injected the sidecar as if it were a whole file.
- Quotas generalize beyond memory types to resources and skills, with purpose
  presets supplying ratios when quotas are absent.
- dedup_turns keeps a per-session ledger at {session_uri}/.recall_log.json so
  every harness inherits cross-turn dedup; exclude_uris remains as the
  stateless fallback.
- Rendering flattens to one <memory uri=... type=... score=... detail=...>
  element per entry. Every tier carries its URI, so the model can always drill
  down through the MCP read tool.
- Query expansion and digest rewriting are opt-in and fail closed: both have
  timeout fuses, and a failed rewrite still returns the unrewritten block.
  Retrieval failures are counted into stats rather than silently yielding an
  empty block.

Plugins now send one context request, falling back to /recall and then to raw
find on older deployments, and cache that outcome so only the first turn pays
for the probe. The tri-state recallRewrite knob chooses between local host-CLI
compression and the server digest, and client-side settings move to a plugin
section in ovcli.conf.

* refactor(retrieval): give context tiers a per-category default

The tier ladder assumed `abstract` is a cheap summary. For memory files it
is not: the memory writer stores the whole stripped body in that scalar
because it doubles as the embedding text, so `abstract` costs the same as
`full` and the ladder runs `uri < overview < abstract = full`. Two of the
model's properties fell out of that: exempting `abstract` from the per-entry
cap let a single entry eat several times the budget, and `detail` — which
only ever set a ceiling — collapsed to two distinguishable behaviours across
its four values, since `auto` already allowed `full` for memory.

Tiers now come from a per-category constant table that treats the storage
shape as a given: `events` starts at overview (the one memory type whose
`# Summary` extraction is a real compression) and may deepen to full on
leftover budget; every other category is served at `abstract`, which for
memory already is the complete file at zero read cost and for resources and
skills is the generated 256-char summary. The table carries the note to move
`events` back to `abstract` once the writer stores a separate summary scalar.

Falling out of that: prefetch now reads only the candidates whose planned
tier needs a body rather than every candidate, `detail` becomes a real pin
(start and ceiling) and additionally accepts a per-category map, and
`full_score_threshold` is gone — leftover budget is spent in score order
instead of behind an absolute threshold the observed score band cannot
support. `auto` is still accepted on the wire as a synonym for "unset".

Assembly fixes found alongside:

- Removing the abstract cap exemption would turn an oversized abstract into
  a bare URI, so it now falls back to overview first — for memory that is a
  cheaper substitute, not a step up.
- Rewrite timeouts were reported as failures on Python 3.10, where
  `asyncio.TimeoutError` is a separate class from the builtin.
- `stats.rewrite_usage` read `token_tracker` off `VLMConfig`, which has no
  such attribute; usage was structurally always null. It now reads the model
  instance's tracker and reports only when the call count moved by exactly
  one, since that tracker is shared.
- A single malformed ledger record made every deduped recall in that session
  fail, and the file was never rewritten, so it could not heal. Records are
  now coerced on read and dropped on the next write, along with records left
  ahead of the clock by an archive rotation.
- Entries served as a bare URI no longer enter the dedup cooldown: they lost
  to budget pressure, not to the reader having already seen them.
- The render envelope only neutralised a literal `</memory>`, so a body could
  forge a sibling entry with its own uri, type and score.
- Flat-mode gathering re-derived the category from the URI, reading
  `viking://resources/backup/memories/events/log.md` as an event.
- Cooled and excluded URIs are compensated with extra rows, so a fully cooled
  bucket falls through to the next-best hits instead of coming back empty.
- `/recall` quotas overlay the v1 bucket defaults again; `{"events": 5}` had
  started dropping the other three buckets.
- The MCP `recall` signature sent its own defaults as if the caller had, which
  resolved a different profile than `POST /recall`; an unknown `detail` value
  raised `KeyError` through the whole call instead of degrading.

* feat(codex): inject profile context on session start

Reuse the shared profile builder for startup, clear, and resume hooks while preserving archive injection and orphan-session status output.

Co-authored-by: TRAE CLI <noreply@bytedance.com>

* fix(retrieval): raise rewrite timeout default to 30s

* docs(agents): document low-latency recall settings

* fix(codex): prefer luna as recall compressor fallback

* refactor(plugins): unify recall compression setting

* feat(plugins): enable recall compression by default

* docs(agents): use absolute links in image docs

* fix(retrieval): address context assembly review feedback

Co-authored-by: TRAE CLI <noreply@bytedance.com>

* test: trim redundant context assembly coverage

* fix(retrieval): address second-round context assembly review

- Drop the backticked `/search` from the deprecated-recall row in both API
  overviews. The reference checker scans the whole row after the method cell
  for backticked paths, so it read the description as a route named
  `POST /search` and Build Docs failed on an unknown, undocumented route.
- Accept ovcli.conf's full field set in both Python readers. The file's schema
  belongs to the Rust CLI, which writes `root_api_key`, `output`,
  `echo_command`, `show_progress` and `verbose` and ignores unknown keys; the
  two Python readers had drifted into stricter subsets, so the shipped example
  already failed to load in both. Adding the new `plugin` section to a working
  ovcli.conf would have broken `ov doctor` and every SDK client the same way.
- Return 400 from `mode="context"` for a request `mode="list"` also rejects.
  Retrieval validates query and image_url before searching, and the gather
  fuse swallowed that rejection along with genuine scope failures, so a body
  of `{"mode":"context"}` came back 200 with an empty block instead of the
  documented parameter error. Runtime failures still degrade into
  `stats.retrieval_errors`.
- Let a context request that asks for a server-side digest outlast the
  server's rewrite fuse. The plugin's ordinary 15s request timeout is shorter
  than the 30s fuse, so a rewrite that finished inside its own budget was
  aborted client-side, discarding the whole response — including the
  uncompressed block the server returns when a rewrite fails — and falling
  back to `/recall`. The deadline is only extended when the body actually
  requests a rewrite, and `OPENVIKING_RECALL_CONTEXT_TIMEOUT_MS` /
  `plugin.recallContextTimeoutMs` pins it.

* chore(plugins): sync shared modules into the zcode snapshot

* fix(retrieval): align context quotas and plugin defaults

Restore cross-domain coding recall, reuse authoritative actor resource
scopes, and make bucket quotas the sole width control in purpose mode.
Keep plugin defaults server-owned while preserving explicit legacy limit
settings through quota conversion.

Co-authored-by: TRAE CLI <noreply@bytedance.com>

* fix(retrieval): preserve recall compatibility

Restore the deprecated recall threshold default, distinguish successful empty rewrites from compressor failures, and document legacy quota floors across coding-agent plugins.

Co-authored-by: TRAE CLI <noreply@bytedance.com>

---------

Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: qin-ctx <qinhaojie.exe@bytedance.com>
2026-08-05 12:31:21 +08:00
zgy 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 8e342055f8.

* Simplify fixed code skeleton summary route

* Inline process skeleton rendering

* Simplify code skeleton routing entrypoints

* Fix code summary review issues

* Address final code summary review feedback

* Route failed tags skeletons to LLM fallback

* Restore CUDA and TS extension routing

* Improve code skeleton query coverage

* Route semantic code detection through skeleton support

* Move process skeleton engine into ast package

* Admit skeleton-supported files during directory scan

* Align code summary docs after main merge

* Reduce code skeleton fallback log verbosity

* chore: require grep-ast 0.9.0
2026-07-31 11:38:57 +08:00
yufeng 229300e886 fix(codex): preserve session tool activity (#3579) 2026-07-29 12:05:48 +08:00
t0saki 29ba81b53e feat(plugins): identify memory plugin traffic with a User-Agent (#3532)
Every harness memory plugin sent OpenViking requests under Node's default
`user-agent: node`, so server-side and gateway logs could not attribute
traffic to a harness or a plugin version.

Send `openviking-memory-<harness>/<version>` from all six harnesses
(claude-code, codex, opencode, pi, cursor, trae) on both the data plane
and the MCP proxy. Versions come from each harness's own manifest, or
from OPENVIKING_INTEGRATION_VERSION for cursor/trae; an unreadable
manifest degrades to 0.0.0 rather than throwing inside a short-lived hook.

The header is purely informational — neither the open-source server nor
the commercial gateway consumes it, and no auth or identity header
changes.
2026-07-27 15:31:21 +08:00
agent 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
2026-07-16 15:33:27 +08:00
t0saki 6f10e26361 perf(plugins): batch add-message writes and shared async detach across memory plugins (#3261)
* Batch add-message writes across memory plugins

* fix(plugins): stop batch enqueue at first failure and bump plugin versions

Keep pending-queue entries a contiguous prefix when a mid-stream enqueue
fails: consumers mark the first sent+queued payloads as captured, so a
queued entry after a gap could silently drop the gapped message.

Bump claude-code 0.4.3, codex 0.7.3, opencode 0.2.3, cursor/trae 0.1.2 so
existing installs pick up the batch write path.
2026-07-15 18:20:10 +08:00
t0saki 82999c8b4b fix(plugins): pin MCP-Protocol-Version header to the negotiated version (#3214)
* fix(plugins): pin MCP-Protocol-Version header to the negotiated version

The shared stdio proxy forwarded the client's un-negotiated
protocolVersion ask as the MCP-Protocol-Version header on initialize and
cached it for follow-up requests, never reading the version the server
actually negotiated in the initialize response. Strict streamable-HTTP
upstreams (e.g. modelcontextprotocol/go-sdk servers) validate that header
on every request and answer HTTP 400 'Unsupported protocol version'
before negotiation can run, so clients on a newer spec than the upstream
(Trae sends 2025-11-25) failed MCP registration outright.

Send the proxy default (2025-06-18) while initializing, adopt
result.protocolVersion from the initialize response for all subsequent
requests, and keep the client's ask intact in the forwarded initialize
body so end-to-end version negotiation still happens.

* chore(plugins): bump patch versions for the MCP proxy protocol fix

claude-code 0.4.2, codex 0.7.2, cursor 0.1.1, trae 0.1.1, opencode 0.2.2.
2026-07-13 15:12:43 +08:00
yufeng 0a14967f6b docs: align MCP references with implementation (#3146)
* docs: align MCP references with implementation

* docs: fix remaining factual drift

* docs: correct remaining API examples

* docs: fix observer status response type
2026-07-11 15:54:07 +08:00
t0saki a878fc1a96 chore(plugins): bump memory plugin versions (#3126) 2026-07-10 17:38:12 +08:00
t0saki 2a81edc707 Add workspace peer mode for memory plugins (#3099) 2026-07-09 17:53:36 +08:00
t0saki b511d91ee5 feat(plugins): retrofit OpenCode and pi memory integrations (hybrid MCP, shared lib, 4-harness installer) (#3079)
* feat(plugins): align opencode and pi memory integrations

* fix(installer): tolerate missing optional harness CLIs

* fix(installer): install opencode file wrapper

* fix(opencode): import path for logger initialization

* fix(installer): register pi extension after copy

* feat(plugins): use MCP for opencode integration

* docs: move OpenCode and pi integrations to dedicated pages

Promote the OpenCode plugin and pi extension out of the community-plugins
page into their own numbered agent-integrations pages (10-opencode, 11-pi,
en + zh), update the overview routing table, and refresh the OpenCode image
cards to the hybrid MCP architecture (unified installer, openviking_* MCP
tools, ovcli.conf credentials).

* docs: bare TOS installer commands and reference more examples

Drop --harness from TOS-mirror install commands (image cards use the bare
installer URL, matching the claude-code/codex cards); add Open WebUI tool
server and an examples/ pointer to the community-plugins page (en + zh).

* docs: bare TOS installer commands across agent-integration pages

TOS-mirror install commands carry no flags anywhere; the installer wizard
asks for source, harnesses, language, and credentials.
2026-07-08 15:54:37 +08:00
t0saki f905562534 feat(plugins): stdio MCP proxy, remote marketplace install, and type-quota recall for memory plugins (#3039)
* feat: add memory plugin mcp harness

* refactor: vendor shared memory plugin modules

* feat: add type quota recall api

* feat: commit codex memory by token threshold

* feat: capture codex tool calls as parts

* feat: add claude skill experience recall

* chore: fix lint in type quota recall server files

* feat: remote marketplace install with unified openviking naming

- Fix root .claude-plugin/marketplace.json git-subdir discriminator key
  ("type" -> "source"); claude plugin validate now passes.
- Unified installer gains --source remote|archive|dev: remote registers a
  synthesized git-subdir marketplace for Claude Code and a git marketplace
  for Codex (no repo clone); archive consumes the slim TOS marketplace zip;
  dev registers the checkout's examples/ directory for both harnesses.
- One marketplace name (openviking) across all modes and harnesses, so the
  plugin id is always openviking-memory@openviking; installer migrates old
  openviking-plugins-local registrations and config.toml sections.
- Restore legacy Claude Code (<2.0) support: claude mcp add (stdio proxy)
  plus node-based hooks merge into ~/.claude/settings.json.
- Restore optional statusline registration (fetches sources on opt-in).
- Checkbox TUI harness selection via /dev/tty with non-tty fallback.
- Add examples/.agents/plugins/marketplace.json so Codex directory installs
  drop the synthetic symlink marketplace.
- Add shared setup wizard (scripts/setup.mjs) for pure-marketplace installs.
- release-tos.yml: upload memory-plugin-shared/install.sh and build/upload
  the memory-plugin-marketplace zip; tos-install.sh prefers it and pins all
  fetches to TOS via OPENVIKING_SHARED_INSTALL_URL.
- CI: bash -n on installer scripts; marketplace contract tests updated.

* fix(installer): register Claude remote marketplace as a directory

File-type marketplaces (bare marketplace.json path) make Claude Code derive
a wrong installLocation and 'marketplace update' fails with EISDIR. Write
the synthesized manifest to <dir>/.claude-plugin/marketplace.json and add
the directory instead; compare registered sources by exact match so the
old file registration migrates cleanly.

* feat(statusline): show model name and native-style context percentage

A custom statusLine replaces Claude Code's native line including its context
indicator, so reproduce it from the statusline stdin payload: 'Fable 5 ·
ctx 42%' right after the health segment, with native color thresholds
(<70% dim, 70-89% yellow, >=90% red). Falls back from used_percentage to
remaining_percentage to token counts, and stays visible in bypass mode
since it describes the CC conversation, not OV. Opt out with
OPENVIKING_STATUSLINE_CTX=off. Line cap raised 80 -> 100 visible chars.

* fix(installer): keep checkout progress off stdout in plugin_dir_on_disk

Callers capture the function's stdout, so ensure_checkout's info lines were
concatenated into the statusline command registered in settings.json.

* fix(installer): re-register codex git marketplace instead of upgrading

Codex doesn't expose which --ref a git marketplace was added with, and
'marketplace upgrade' refreshes the old ref — so a URL match must not skip
re-registration or a ref override installs the wrong snapshot. Also remove
the stale pre-unification plugin cache directory during migration.

* fix(installer): include .agents in codex sparse checkout

A plugin-dir-only sparse checkout omits the repo-root marketplace manifest
and fails with 'marketplace root does not contain a supported manifest'.
Adding --sparse .agents keeps the snapshot slim (~7.5M vs full repo).

* feat(installer): bilingual prompts, dist channel selection, and TOS git marketplace for codex

- Interactive language selection (English/中文, --lang, auto-detected from
  locale); every user-facing prompt is bilingual.
- Download-source selection (--dist github|tos, prompted interactively):
  github keeps the remote marketplaces; tos serves GitHub-blocked regions.
- Credentials step now always shows the current ovcli.conf values (masked
  key) and offers keep-or-reconfigure instead of silently reusing them.
- Codex on TOS installs from a TOS-hosted git repo over dumb HTTP and keeps
  remote updates (codex plugin marketplace upgrade); falls back to the
  archive directory if the repo is unavailable. release-tos.yml builds and
  uploads the single-commit bare repo (repack + update-server-info).
- Claude Code on TOS warns that directory marketplaces cannot auto-update.
- tos-install.sh bootstraps shrink to TOS_BASE + --dist tos.
- Docs (READMEs, agent-integrations pages, image cards, en+zh) now all use
  the single shared installer and drop the deleted wrapper instructions.

* feat(installer): unify all choice prompts on an arrow-key TUI menu

Language, download source, connection mode, keep-or-reconfigure
credentials, statusline enable/replace, and legacy-mode confirmation all
render as the same single-select menu (arrow keys / digit shortcuts /
enter, radio-style highlight) instead of mixed numbered and y/N prompts.
Falls back to numbered input when /dev/tty can't be drawn on and to the
default choice when non-interactive. Free-text fields (URL, API key) stay
line inputs; the harness picker keeps its checkbox multi-select.

* fix(installer): stop piping plugin lists into grep -q under pipefail

grep -q exits on first match and SIGPIPEs the producer, so with pipefail
the 'codex plugin list | grep -q' check read as a miss every time (codex's
list is long; claude's short list masked the bug). Capture the output and
substring-match in bash instead — validation no longer false-warns.

Also: drop the stdio-proxy line from the Done summary; always offer the
install-source menu unless --dist/--source was given (with a checkout the
menu gains a dev option and defaults to it); surface the Claude-on-TOS
no-auto-update warning at source resolution instead of after install.

* fix: unignore examples/memory-plugin-shared/lib and commit the shared modules

The Python build-artifact 'lib/' gitignore rule silently swallowed the
shared plugin module source, so CI checkouts had only the vendored copies
and sync.test.mjs failed with ENOENT on the source directory.

* fix(recall): budget summary/uri fallbacks and sanitize non-finite scores

max_chars is the recall API's contract, but only full fragments counted
toward it — VikingBot's client-side heuristic, faithfully ported, lets
summary and uri fallbacks render far past the budget (repro: max_chars=100
rendered 548 chars). Every fragment now counts; oversized summaries degrade
to uri fragments and entries that can't even fit a uri line are dropped
(reported via stats.dropped). VikingBot itself is intentionally unchanged.

Also run _sanitize_floats over the /recall response like the neighboring
/find and /search routes, so inf/nan scores return 0.0 instead of a 500.
2026-07-07 12:33:59 +08:00
t0saki aca58bf3b9 feat: TOS release upload + GitHub-free install path for memory plugins (#2575)
* ci: upload source zip and plugin installers to TOS on release

Add a standalone workflow (20. Release TOS Upload) that runs on release
publish (or manual dispatch with a tag for backfill) and uploads:

- the source archive to releases/<tag>/ and releases/latest/
- both memory-plugin install.sh scripts to versioned paths and to
  stable root paths for a China-reachable one-liner URL

Reuses the existing TOS secrets (AK/SK/region/endpoint) with a new
TOS_RELEASE_BUCKET secret so release artifacts stay out of the docs
bucket. Missing secrets skip gracefully (fork-friendly); real upload
failures fail the workflow.

* ci: server-side copy for the latest source zip

* feat(plugins): GitHub-free TOS install path for memory plugins

Domestic users can't reach github.com / raw.githubusercontent.com, so the
existing one-liner installers stall at their step-3 `git clone`. Add a
GitHub-free path that sources everything from Volcengine TOS:

- Both install.sh learn OPENVIKING_REPO_ARCHIVE_URL: when set, fetch the
  source from a zip (curl + unzip) instead of git clone. A
  .openviking-archive-source marker makes re-runs idempotent and refuses
  to clobber a git checkout or unrelated data at REPO_DIR.
- New setup-helper/tos-install.sh bootstrap per plugin: sets the TOS
  archive URL, downloads the real install.sh from TOS to a temp file
  (kept off the stdin pipe so prompts stay interactive), and delegates.
- release-tos.yml uploads both tos-install.sh alongside install.sh.

One-liner for users behind the GFW:
  bash <(curl -fsSL https://ovrelease.tos-cn-beijing.volces.com/claude-code-memory-plugin/tos-install.sh)

The GitHub default path is unchanged; archive mode only activates when
OPENVIKING_REPO_ARCHIVE_URL is set.

* docs: document the TOS (GitHub-free) install path for memory plugins

Main agent-integration docs (zh/en, claude-code + codex) keep the GitHub
one-liner and add the TOS equivalent for regions where GitHub is hard to
reach. The CDN integration cards switch their install one-liner to the TOS
bootstrap only, since that gallery is served where GitHub raw is unreliable.

* docs: trim the TOS install note to one line
2026-06-15 14:20:09 +08:00
Qin HaojieandMijamind719 8353976bc8 feat(plugins): use actor peer scope (#2595)
* feat(plugins): use actor peer scope

* docs(openclaw): clarify actor peer recall scope

---------

Co-authored-by: Mijamind719 <mijamind@163.com>
2026-06-14 10:15:27 +08:00
Qin Haojie fff86058ac feat(core): 支持用户和 peer 级内容目标 (#2564)
* feat(core): support user-scoped content targets

* fix(core): handle scoped skill updates

* fix(core): keep skills user scoped

* chore: drop incidental formatting changes

* fix(storage): revert shared parent existence helper

* docs: update user content target docs

* fix(resource): canonicalize watch cancellation targets
2026-06-12 17:52:44 +08:00
Baokaiandjlcbk 040920b10d feat(claude-code-plugin): 为短生命周期 Coding 工具增加本地 Pending Queue (#2421)
* feat(claude-code-plugin): add local pending queue for offline resilience

When the OpenViking server is temporarily unreachable, write operations
(addMessage, commitSession) now serialize payloads to a local pending
queue at ~/.openviking/pending/. On the next session-start, the queue
is replayed when the server is healthy again.

Key changes:
- New pending-queue.mjs module with enqueue/replay/cleanup/dedup
- Health gate moved after transcript parsing (auto-capture, subagent-stop)
- Retryable failures (network/5xx/408/429) auto-enqueue locally
- Non-retryable failures (401/403/404/422) warn and skip
- Atomic file writes via temp+rename, restrictive permissions (0o700/0o600)
- SHA-256 prefix-based dedup to avoid duplicate queue entries
- Replay stops on first retryable addMessage failure to preserve ordering
- Subagent commit intent generated even when all turns go to pending queue

Addresses reviewer feedback on PR #2421.

* test(claude-code-plugin): cover pending queue replay

---------

Co-authored-by: jlcbk <jlcbk@users.noreply.github.com>
2026-06-12 11:33:06 +08:00
t0saki 7237ac611c fix(plugins): skip shell aliases when wrapping extra launch commands (#2471)
Adding a shell-alias name (e.g. `cc` from `alias cc=claude`) to
OPENVIKING_CC_WRAP_EXTRA / OPENVIKING_CODEX_WRAP_EXTRA broke the wrapper:
bash expands the alias mid-eval and clobbers the base `claude`/`codex`
function (so `command cc` ends up running the C compiler), while zsh
aborts with a parse error on every shell start. Guard the wrapper-defining
loop to skip names that are already shell aliases — an alias already
routes through the base wrapper once it expands, so it needs no function.
Also reject heads starting with `-`, which `alias`/`command` would
otherwise misparse as an option.

Also document the custom-launch-command feature and the alias guidance:
- 8 agent-integration docs (en/zh main + CDN cards): brief install note,
  plus two troubleshooting rows (wrapper-not-sourced, alias gap)
- claude/codex plugin READMEs (+ README_CN, which was missing the section
  entirely): wrap the real target command, never the alias name
2026-06-06 12:40:14 +08:00
t0saki 9a329c2e05 feat(plugins): wrap custom launch commands; align codex installer UX with claude (#2464)
Wrap extra launch commands besides `claude` / `codex`:
- wrapper.sh (both plugins): factor credential injection into a helper and
  read $OPENVIKING_{CC,CODEX}_WRAP_EXTRA — a ';'-separated list of extra launch
  commands (e.g. a custom alias `claude-w`, or a multi-word launcher `ccr code`
  / `aiden x claude`). Single-word entries always inject; multi-word entries
  inject only when the leading args match the sub-command, so other uses of
  that command pass through untouched. Portable to bash and zsh (manual
  parameter-expansion splitting, no unquoted word splitting).
- install.sh (both plugins): collect the list (interactive prompt or the
  $OPENVIKING_{CC,CODEX}_WRAP_EXTRA env var), normalize it, and persist it in
  the rc marker block; reuse the existing value on re-run.

Codex installer UX parity with the claude-code installer:
- TTY-aware colored step output (info/warn/err/ask/heading).
- Interactive ovcli.conf setup: reuse existing / choose self-hosted vs
  Volcengine Cloud / enter URL+key, written via node (no jq dependency) and
  merged so extra fields (account/user) are preserved.
- Degrades to non-interactive (existing config / env vars) when stdin is not
  a TTY (e.g. `curl | bash`). All codex-specific install logic (marketplace,
  config.toml, cache, hooks.json, .mcp.json rendering) is unchanged.

READMEs document wrapping extra launch commands.
2026-06-05 18:15:26 +08:00
Zayn Jarvis 6b3d261b61 docs: remove stale agent header references (#2462) 2026-06-05 17:27:07 +08:00
Qin Haojie 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
2026-06-05 10:55:48 +08:00
t0saki e8acebbb46 fix(cc-memory-plugin): record tool calls/results as structured tool parts (#2340)
* fix(cc-memory-plugin): record tool calls/results as structured tool parts

auto-capture.mjs and subagent-stop.mjs inlined tool_use input and
tool_result output into the message content (`[tool: NAME]\n{input}` /
`[tool result]`) and sent content-only, so OV stored each turn as a
single text part and could not separate calls from results. Tool results
were additionally dropped entirely (TOOL_RESULT_MAX_CHARS=0).

Emit structured `tool` parts (tool_id / tool_name / tool_input /
tool_output / tool_status) and send via parts-mode, which
lib/ov-session.addMessage already supports. tool_result blocks are
labelled with the matching call name (looked up by tool_use_id) and
their output is captured bounded to 2000 chars. The legacy inlined
`text` is kept solely to drive the unchanged capture heuristics
(length / keyword), so capture decisions are unaffected.

* chore(cc-memory-plugin): bump version to 0.2.2

* fix(cc-memory-plugin): make installer update an already-installed plugin

install_modern() used marketplace add + plugin install, both no-ops on an
existing install — re-running the installer after a version bump left the
old cached copy in place. Detect the already-present case and use
marketplace update + plugin update instead, which re-sync the catalog from
source and apply the new version (restart required).
2026-05-31 18:03:56 +08:00
Qin Haojie bb22bac435 fix(security): remove stale critical dependency locks (#2242) 2026-05-26 18:10:01 +08:00
t0saki 527d68d352 refactor(plugin/{codex,claude-code}): extract installer wrapper to checked-in file (#2026)
* refactor(plugin/codex): move shell wrapper to standalone rc file

The installer-emitted codex() wrapper had grown to ~60 lines of shell
function body inlined as a marker-delimited block inside the user's
~/.zshrc / ~/.bashrc. Every upgrade required the awk-strip-and-append
dance, which had a known edge case (rc with begin-marker but no
end-marker) we'd already had to harden against, and the inline noise
was hostile to anyone reading their own rc.

This commit switches to the standard pyenv / nvm / fnm pattern:

- Wrapper body lives in its own file at ~/.openviking/codex-plugin.rc.sh
  (path overridable via OPENVIKING_CODEX_WRAPPER_RC). Full overwrite on
  every install — no marker logic inside the wrapper file itself.

- The user's shell rc gets a single one-line source hook, still wrapped
  in marker comments for cleanup-on-uninstall:

    # >>> openviking-codex-plugin >>>
    [ -f "$HOME/.openviking/codex-plugin.rc.sh" ] && . "..."
    # <<< openviking-codex-plugin <<<

  Since the content of this block never changes across installs, the
  marker-replacement logic only triggers the legacy-cleanup path once
  when upgrading from a pre-rc-split install that inlined the full
  wrapper.

User-visible improvements:

- ~/.zshrc OV-plugin block: ~70 lines → 3 lines.
- `cat ~/.openviking/codex-plugin.rc.sh` shows the wrapper directly.
- Uninstall is just `rm ~/.openviking/codex-plugin.rc.sh` + delete the
  3-line block — no awk required.
- Upgrades touch the rc file at most once (to install the source hook);
  subsequent installs only rewrite the wrapper file.

Verified end-to-end: stale install with the old inline wrapper got the
3-line source hook substituted in place; `source ~/.zshrc && type codex`
showed the new wrapper loaded from the standalone file.

* refactor(plugin/codex): source wrapper from repo path, drop heredoc dance

Follow-up to the rc-split commit. Instead of embedding the wrapper body
as a heredoc inside install.sh and writing it to a copy under
~/.openviking/, the wrapper now lives as its own checked-in file at
examples/codex-memory-plugin/setup-helper/wrapper.sh. The user's shell
rc sources that file directly from the cloned plugin checkout (the path
the installer already manages via git fetch + reset --hard).

What this buys:

- Wrapper diffs are real diffs — code review sees `+ codex() { ... }`
  rather than `+ heredoc lines inside install.sh that produce
  ~/.openviking/codex-plugin.rc.sh`.
- No copy step in the installer means no installer code path for "did
  the user accidentally edit ~/.openviking/codex-plugin.rc.sh?" or "is
  the copied file in sync with what the installer would produce now?"
- Updates ride for free on `git pull` / the installer's existing
  fetch+reset. No "re-run installer to refresh the wrapper" step.
- Uninstall is just `rm ~/.openviking/openviking-repo` (or just leave it
  — the source hook will silently no-op when the file is gone, since
  it's gated with `[ -f ... ] && .`).

Installer shrinks from ~420 lines (with the inline heredoc) to ~340.
The wrapper is unchanged content-wise; this commit only moves where it
lives.

* refactor(plugin/claude-code): move shell wrapper to standalone rc file

The installer-emitted claude() wrapper had been inlined as a
marker-delimited block in the user's ~/.zshrc / ~/.bashrc. Every upgrade
required the awk-strip-and-append dance, the inline noise was hostile
to anyone reading their own rc, and there was a known footgun: if the
END marker got hand-deleted from the rc, the next install's awk-strip
would drop everything from the BEGIN marker to EOF.

Switch to the standard pyenv / nvm / fnm pattern, mirroring what the
codex-memory-plugin installer now does (see #2023):

- Wrapper body lives in its own file at ~/.openviking/claude-plugin.rc.sh
  (path overridable via OPENVIKING_CLAUDE_WRAPPER_RC). Full overwrite on
  every install — no marker logic inside the wrapper file itself.

- The user's shell rc gets a single one-line source hook, still
  marker-wrapped for clean uninstall:

    # >>> openviking claude-code memory plugin >>>
    [ -f "$HOME/.openviking/claude-plugin.rc.sh" ] && . "..."
    # <<< openviking claude-code memory plugin <<<

  Content is constant across installs, so the marker-replacement logic
  only triggers the legacy-cleanup path once (when upgrading from a
  pre-rc-split install that inlined the full claude() function body).

User-visible improvements:

- ~/.zshrc OV-plugin block: ~16 lines of wrapper body → 3 lines.
- Wrapper body is a real file you can `cat` / `diff` / restore from
  source control; no need to re-run the installer to inspect it.
- Uninstall: `rm ~/.openviking/claude-plugin.rc.sh` + delete the 3-line
  marker block.
- The END-marker corruption footgun is gone, since the marker block
  content is bytestring-stable and the awk-strip only runs when both
  markers are present anyway.

No behavior change to the wrapper itself (still pulls url/api_key from
ovcli.conf via jq).

* refactor(plugin/claude-code): extract wrapper to checked-in setup-helper/wrapper.sh

Squash-style follow-up to the previous rc-split commit on this branch:
now that the wrapper lives in its own file conceptually, just check it
in at examples/claude-code-memory-plugin/setup-helper/wrapper.sh and
have the user's shell rc source it directly from the cloned plugin
checkout. No copy step, no heredoc dance in install.sh.

Why this is better than the previous approach (wrapper body embedded as
a heredoc in install.sh, written to a copy in ~/.openviking):

- Wrapper is a real reviewable file. Diffs show `+ claude() { ... }`,
  not "+ heredoc lines that produce the wrapper".
- Updates ride on the installer's existing `git fetch + reset --hard`
  step — no separate "re-run installer to refresh the copy" path.
- One less source of truth (no $HOME copy that can drift from the
  installer's intent).
- Uninstall: `rm ~/.openviking/openviking-repo`; the source hook in the
  rc silently no-ops via `[ -f ... ] && .`.

The previous commit on this branch already shrunk the rc block from
~16 inline lines to 3 (marker + source hook + marker). This commit just
moves the wrapper body from "embedded in installer" to "checked into the
repo at a stable path", with no behavior change to the wrapper itself.
2026-05-13 22:02:51 +08:00
t0saki 31bcfd2bd7 feat(claude-code-plugin): session-start profile injection, /ov status command, tool-output capture cleanup (#1914)
* feat(claude-code-plugin): inject user profile + memory listings on session start

Previously, profile/preferences/entities only reached the agent when the
user's prompt happened to trigger semantic auto-recall (UserPromptSubmit).
Trivial first prompts (e.g. `git status`) left the agent with no identity
context.

Session-start hook now always builds a profile injection block —
profile.md plus a description-annotated recursive ls of preferences/ and
entities/ — composed into the same <openviking-context source="..."> envelope
that already carries archive context on resume/compact. Subagents are
unaffected (they go through subagent-start.mjs).

Budget enforcement uses a CJK-aware token estimate (codepoint >= 0x3000
counts at 1.5 tokens, else chars/4) so a "10k token budget" reflects real
tokenizer cost for Chinese content rather than the 4-6× undercount the
flat chars/4 heuristic produces.

Profile truncation on overflow keeps the head (identity facts) and tail
(most-recent timeline events), eliding the noisy middle, instead of
hard-cutting at the head.

Each invocation mirrors the composed payload to ~/.openviking/last_inject.md
for user-facing audit.

New env vars / config (config.mjs):
- OPENVIKING_NO_AUTO_INJECT (bool, default false) — kill switch for the new
  injection; auto-recall is unaffected.
- OPENVIKING_PROFILE_TOKEN_BUDGET (int, default 10000) — total cap for the
  block; profile gets up to half, listings split the remainder.

* feat(claude-code-plugin): add /ov slash command for plugin status

Tight five-section status report covering: server URL + /health latency,
resolved identity (account/user/agent), last session-start injection
(size, age, audit-file path), last auto-recall (item count, top score,
token budget use), and toggle state for the three injection paths
(auto-inject / auto-recall / auto-capture).

Final line shows where url + api_key were actually resolved from (env vs
ovcli.conf vs default), per the same priority chain config.mjs uses —
rather than enumerating every file on disk that *could have* contributed.

Reuses existing ~/.openviking/state/ files (last-recall.json,
last-session-event.json) and the audit file written by session-start.mjs;
no new server-side state.

* fix(cc-memory-plugin): drop tool output by default; keep tool input verbatim

After #1849 / #1850 the plugin captured tool I/O at a 4 KB-per-block cap
under one knob (TOOL_BLOCK_MAX_CHARS = 4096). Field thinking surfaced
two refinements:

1. **Tool *output* (tool_result content) is mostly noise for memory
   extraction.** Memory extraction cares about user preferences, project
   context, decisions, and what the agent did — not about the bytes a
   tool happened to return. The agent's prose around the tool call almost
   always summarizes the meaningful bit ("I checked the docs and confirmed
   X"); the raw 4 KB of fetched markdown adds nothing the prose doesn't
   already cover. Storing it just inflates session size and extraction
   token cost.

   Renamed TOOL_BLOCK_MAX_CHARS → TOOL_RESULT_MAX_CHARS and changed
   default to 0. When 0, tool_result blocks are dropped entirely. Operators
   wanting replay-style archives can set >0 to retain truncated output.

2. **Tool *input* should not be truncated.** Inputs are agent-authored
   (URLs, file paths, queries, commands). They're usually short, and a
   pathologically long input is itself signal worth surfacing — a
   memory extractor seeing "agent ran `bash` with a 10 KB script" learns
   something the truncated form would hide.

   Replaced truncateForLog(block.input) with formatToolInput(block.input),
   which JSON-serializes structured inputs but applies no length cap.

Both changes apply symmetrically to auto-capture.mjs and subagent-stop.mjs.

* fix(claude-code-plugin): address Copilot review on PR #1914

Nine review comments, all valid:

profile-inject.mjs:
- header doc said chars/4 but estimateTokens is CJK-aware → fixed
- estimateTokens now exported so callers can log token counts that match
  the budget logic
- elideProfile derived maxChars from maxTokens*4, but the estimator counts
  CJK at 1.5 tokens/char → for CJK profiles the truncated string could
  still bust the token cap. New tokensToCharsBudget() converts using the
  content's actual CJK density
- formatListing always included header + first entry, so very small budgets
  silently violated the cap. Now: stub-out when header alone exceeds
  budget; only emit "+N more" tail when it fits; close silently otherwise
- profileBytes was UTF-16 char count, labeled "B" → renamed to profileChars

session-start.mjs:
- header doc said budget=5000, code default is 10000 → doc fix
- local estimateTokens was flat chars/4 while injection enforces CJK-aware
  budget → import the shared estimator from profile-inject so logs match
  reality
- /health probe ran even when no injection path would fire (e.g.
  NO_AUTO_INJECT=1 + startup) → short-circuit before the network call
- profileBytes references updated to profileChars

ov-status.mjs:
- header doc said "Active config file + env overrides" but the bottom
  block was removed earlier → header fixed to describe Auth source
- auth source detection only considered env + ovcli.conf; could misreport
  "(none)" when key was actually coming from ov.conf claude_code.apiKey
  or server.root_api_key. Now mirrors config.mjs's full priority chain
  (env → ovcli.conf → ov.conf → default)
2026-05-08 18:52:44 +08:00
t0saki acec33bb5f fix(server,plugin): readable OV session id + MCP store role_id (#1895)
* refactor(claude-code-plugin): readable OV session id (cc-<uuid>__agent-<id>)

Replace the SHA-256-derived `cc-<hash>` form with a literal embedding of the
CC session_id, so OV/CC ids can be matched by eye instead of via shasum.
Subagent isolation still works by appending `__agent-<agentId>` to the parent
id, preserving lineage in the string itself.

Old `cc-<hash>` sessions are left untouched (no migration); they expire
naturally as users start new CC sessions.

Docs (zh/en) gain a short subsection explaining the format and where to find
the live cc_session_id ↔ ov_session_id pair (~/.openviking/state/last-capture.json).

* fix(server): MCP store now resolves role_id via shared ctx helper

Messages stored through the MCP `store` tool persisted with `role_id=null`
because that path called `Session.add_message` directly, skipping the HTTP
router's `_resolve_message_role_id` fallback (user.user_id for role=user,
user.agent_id for role=assistant).

Lift the resolver onto `RequestContext.resolve_role_id(role, override=None)`
so both call paths share one implementation:
- HTTP `POST /api/v1/sessions/{id}/messages` now calls
  `_ctx.resolve_role_id(request.role, request.role_id)`.
- MCP `store` tool calls `ctx.resolve_role_id(msg.role)` per message.

Drop the now-vestigial `http_request: Request` parameter from the HTTP
add_message handler (the local resolver was the only thing using it).

* fix(server): address copilot review on PR #1895

- identity.py: rename `role` → `message_role` in resolve_role_id signature so
  it doesn't shadow `RequestContext.role` (the authz role). Also adds blank
  line after ToolContext docstring to satisfy ruff format.
- tests/server/test_api_sessions.py: drop now-unused `http_request=...` and
  the `auth_mode` / `api_key_manager` plumbing from `_call_add_message_route`
  helper and its call sites — these were only needed by the resolver's stale
  `http_request` parameter, which the previous commit deleted.
- tests/server/test_mcp_endpoint.py: add regression test asserting MCP `store`
  now passes the resolved role_id (user.user_id for user, user.agent_id for
  assistant) to Session.add_message. Also fixes a pre-existing import bug
  (`list_dir` → `ls`) that was preventing the whole file from being collected.
2026-05-07 19:22:53 +08:00
t0saki 268147d110 feat(claude-code-plugin): OpenViking statusline (opt-in) (#1890)
* feat(claude-code-plugin): add OpenViking statusline (opt-in)

A one-line OV status renders under the CC input box: server health,
last-turn recall stats, pending capture, and queue alerts. Network
calls share a 5 s file cache and have a 250 ms hard timeout so the
statusline never blocks render.

- scripts/statusline.mjs: main entry, ANSI degrade, 80-char cap
- scripts/lib/state.mjs: atomic JSON state writer + TTL reader
- scripts/lib/server-probe.mjs: cached /health (+ /observer/queue)
- auto-recall / auto-capture: write last-recall.json / last-capture.json
- setup-helper/install.sh: opt-in prompt; replace-or-skip for existing
  user statusline; backup + restore-instructions
- bump plugin version 0.2.0 -> 0.3.0

* fix(claude-code-plugin): give /observer/queue its own 250ms budget

Queue probe was sharing the /health 250 ms budget; on remote servers
where /health used 200ms+, the queue probe got ~50ms or was skipped
entirely, so queue_healthy flapped between false (when /health was
fast) and null (when /health was slow). Result: ⚠ queue badge appeared
intermittently even when the queue was consistently unhealthy.

Worst-case statusline latency goes from 250ms to 500ms; typical case
is unchanged (~150ms) since both endpoints respond in tens of ms when
the server is healthy.

* fix(claude-code-plugin): loosen statusline timeout to 1s, show archive count

- 250ms was too aggressive for remote OV servers; ordinary network jitter
  (200-400ms /health) was producing spurious "OV ✗ offline" flicker.
  Bumped to 1s per endpoint. Worst case render is now ~2s but the 5s
  cache amortises this to once per 5s window per session.

- pending_tokens is a sawtooth: it climbs to commit_threshold then snaps
  to 0 on commit. Showing only "X/20k tok" of a long conversation read
  as "we only captured X tokens", which hid the work already archived.
  Now the statusline also shows "N arch" — the running commit_count
  pulled from the OV session metadata. So a long session now shows
  e.g. "✎ 573/20k tok · 2 arch" instead of just "✎ 573/20k tok".

* fix(claude-code-plugin): drop ⚠ queue badge — false-alarm by design

QueueObserver.is_healthy() is derived from QueueManager.has_errors(),
which is `any(q._error_count > 0 for q in queues)`. _error_count is a
lifetime cumulative counter that never resets, so any server with a
single transient embedding failure ever flips is_healthy to false
forever — even when the next 1000 jobs all succeed.

Real example from a production server: 41 jobs processed, 2 historic
errors (95%+ success rate), is_healthy returns false. The badge then
appears constantly for users whose OV experience is fine.

Removing the badge and the second network round-trip. Connectivity
(OV ✓), recall activity (↩ N mem), and capture progress (✎ N/20k arch)
already cover whether OV is functioning end-to-end.

* feat(claude-code-plugin): four new statusline signals

- ↩ N mem (0.92): max recall score appended in parens. Quality hint
  without an extra segment. auto-recall.mjs now writes top_score in
  last-recall.json.

- ✗ N dropped: turns that auto-capture failed to push this batch. Not
  sticky — auto-capture overwrites last-capture.json each Stop hook,
  so transient failures clear themselves on next success. Sustained
  failures stay visible (which is when the user needs to know).

- 🔗 resumed / 🔗 compact: session-start.mjs writes a 1-min TTL
  event when CC source is resume or compact. Lets the user see that
  OV did re-hydrate context across restarts instead of having to
  guess.

- +N today: cross-session daily commit_count. auto-capture maintains
  daily-stats.json (resets on date rollover). Hidden when 0 to keep
  fresh-day mornings unobtrusive. Distinct from per-session "M arch"
  which only counts the current CC session.

Truncation order verified: server → recall → capture → dropped (alert)
→ resumed (info) → today (info). 80-char cap drops the lowest-priority
tail when the line gets crowded.

* fix(claude-code-plugin): drop "tok" unit from statusline size numbers

Recall side: `tokens_used` is a chars/4 heuristic (estimateTokens in
auto-recall.mjs), not real tokens. For CJK-heavy text the heuristic
underestimates by 2-4x, so labelling it "tok" is misleading.

Capture side: `pending_tokens` comes from the server, but the server's
own counter is also approximate. Mixing the two under the same label
invites the wrong mental model.

Just drop the unit. The magnitude is meaningful on its own (1.2k =
medium injection, 573/20k = 3% of next archive). Configuration field
names (recallTokenBudget, commitTokenThreshold) keep "Token" so we
don't churn user-facing config.

* fix(claude-code-plugin): drop recall size number — heuristic was misleading

The "1.2k" between mem count and latency was estimateTokens(text) =
ceil(text.length / 4) on the assembled injection block. For CJK-heavy
content the heuristic underestimates by 2-4x, which is enough that
showing the number does more harm than presenting count + score +
latency alone.

Capture side keeps "573/20k" because the server reports pending_tokens
itself (more accurate, and the ratio against threshold is meaningful
even if the absolute count is approximate).

* fix(claude-code-plugin): always emit session-event marker on resume/compact

Statusline expected `🔗 resumed/compact` to reflect that the event happened,
but session-start.mjs only wrote the marker when `formatArchiveContext` had
something to inject. Fresh sessions with no prior archive saw a `/compact`
silently — statusline showed nothing, leaving the user wondering whether the
hook fired at all.

Move the writeJsonState call ahead of the no-archive early return and tag
the payload with `had_context: false` for the empty case. The badge now
fires on every resume/compact event with a 1-minute TTL.

`✎` capture pending is unaffected — that segment is gated on
`cc_session_id === sessionId` and after `/branch` there's no Stop hook for
the new session yet, which is correct (stale capture from a different
session would be misleading).

* docs(claude-code-plugin): add STATUSLINE.md personalization guide

Statusline has more knobs than env vars expose — segment ordering, colors,
composing with another statusline, custom segments, state file shapes — and
the integration doc is the wrong venue for that level of detail. Add a
recipe-style guide aimed at an AI assistant reading it end-to-end, so users
can ask Claude Code "personalize my statusline" instead of spelunking source.

- examples/claude-code-memory-plugin/docs/STATUSLINE.md: recipes (drop a
  segment, recolor, compose, reset state, add a custom segment) + state
  file schemas + pointers to the canonical files. Defers env-var reference
  back to docs/en/agent-integrations/02-claude-code.md.
- install.sh: print a copy-pasteable seed prompt at the end of install. Not
  intrusive — no auto-launch, just a tip the user can ignore.
- docs/{en,zh}/agent-integrations/02-claude-code.md: cross-link the new doc
  from the Statusline section.

* docs(claude-code-plugin): anchor STATUSLINE.md paths to install location

Recipes referenced \`scripts/statusline.mjs\` etc. with no anchor, so an
agent reading the doc had no way to resolve them — `~/.openviking/openviking-repo/examples/claude-code-memory-plugin/scripts/...`
is far enough off the beaten path that "go look in scripts/" doesn't land.

Define \`\$REPO\` / \`\$PLUGIN\` / \`\$STATE\` once at the top with how to
verify each (jq on settings.json, find as fallback), then propagate the
prefixes through every recipe. The install seed prompt already passes the
absolute path of STATUSLINE.md, so the chain is now self-contained.

* feat(claude-code-plugin): segment glossary + yellow ⚠ slow + dual-purpose install tip

Three small refinements after seeing the statusline in the wild:

- statusline.mjs: split the unhealthy branch — `OV ⚠ slow` (yellow) when
  the probe times out, `OV ✗ offline` (red) when it errors. Slow ≠ dead;
  red was alarmist for transient lag (e.g. remote SaaS GC pauses).

- examples/claude-code-memory-plugin/docs/STATUSLINE.md: add "What each
  segment means" — a full glossary covering every state combination
  (✓/⚠/✗/⚡, ↩, ✎ in its three forms, dropped, 🔗 resumed/compact, +N
  today), plus a "missing when?" troubleshooting list. The integration
  docs only had four example lines, two of which were stale; the canonical
  reference now lives next to the code.

- docs/{en,zh}/agent-integrations/02-claude-code.md: refresh the example
  block (drop stale `1.2k tok` / `12k/20k tok`, add ⚠ slow + 🔗 resumed
  + +N today rows), and broaden the cross-link to advertise both
  explanation and personalization.

- install.sh: rewrite the seed prompt as "walk me through what each
  segment means, then ask if I want to personalize" — covers the more
  common "what does this badge mean?" path before customization.

* docs: link STATUSLINE.md via absolute GitHub URL, not relative path

VitePress only ships docs under \`docs/\`, but STATUSLINE.md lives in
\`examples/claude-code-memory-plugin/docs/\` (next to the plugin code, where
it logically belongs). Relative \`../../examples/...\` resolved on GitHub
but 404'd on the published docs site.

Use an absolute https://github.com/volcengine/OpenViking/blob/main/...
URL — works in both renders, and a parenthetical note tells readers why.

* docs: drop the parenthetical about why the link goes to GitHub

It was meta — readers don't need to know why the link's absolute. Just click.

* docs(claude-code-plugin): move STATUSLINE.md to plugin root

A docs/ folder with one file is awkward when README.md and README_CN.md
already sit at the plugin root. Moves STATUSLINE.md alongside them and
fixes up:

- Stale opening line that pointed at the integration doc for the segment
  glossary — that glossary now lives in STATUSLINE.md itself, so the
  cross-reference is just for env vars.
- Drop the "(path notation defined just below)" parenthetical (meta).
- Update the install seed prompt and the en/zh integration cross-links to
  the new path.

* fix(claude-code-plugin): address Copilot review on PR #1890

Code:
- state.mjs: derive STATE_DIR from \$OPENVIKING_HOME (with ~ expansion) so
  the override the docs already advertised actually works. Default
  unchanged. Was hard-coded to homedir().
- auto-recall.mjs: rename \`session_id\` → \`cc_session_id\` in last-recall.json
  to match last-capture.json / last-session-event.json. STATUSLINE.md
  schema already used \`cc_session_id\`. No reader filtered on the recall
  field, so this is a schema-cleanup, not a behavior change.
- install.sh: quote the plugin path inside the JSON \`command\` value, so
  CC's /bin/sh -c invocation tolerates spaces / metacharacters in
  \$REPO_DIR (custom OPENVIKING_REPO_DIR locations).
- install.sh: mktemp inside ~/.claude/ instead of \$TMPDIR, so the final
  rename is within one filesystem (atomic). Was crossing tmpfs/$HOME on
  Linux, where \`mv\` falls back to copy+unlink and isn't crash-safe.

Comments / docs (drift from earlier "drop tok / 250→1000ms" passes):
- server-probe.mjs: header comment said "Hard 250 ms" while the constant
  is 1000. Replaced with a forward-reference to the constant block which
  already explains the choice.
- README.md / README_CN.md: refresh the example block (drop \`1.2k tok\` /
  \`12k/20k tok\`, add \`⚠ slow\` / \`🔗 resumed\` / \`+N today\` rows), correct
  the hard-timeout sentence (250 ms → 1 s), cross-link STATUSLINE.md.
- install.sh: the \`info\` sample at registration time was also stale.

* chore(claude-code-plugin): version 0.3.0 → 0.2.1

Statusline is additive and opt-in — no API breaks, no behavior change for
existing installs that skip the prompt. A patch bump fits better than a
minor.
2026-05-07 16:38:15 +08:00
t0saki 5576bb2842 fix(cc-plugin): drop --scope from plugin commands, add legacy install path (#1876)
* fix(cc-plugin): drop --scope from plugin commands, add legacy-mode install path

- Removed `--scope user` from `claude plugin marketplace add` and
  `claude plugin install` everywhere (install.sh + READMEs + docs).
  These commands default to user scope already, and older 2.0.x builds
  (e.g. 2.0.76) reject the flag outright. Kept `--scope user` on
  `claude mcp add` because its default is `local` (current-project only)
  and the flag has been supported since MCP first shipped.
- install.sh now probes for `claude plugin` subcommand existence rather
  than parsing version strings. If absent, prompts the user to enable
  legacy compatibility mode, which wires the same functionality through
  `claude mcp add` + a JSON-merge into ~/.claude/settings.json. The
  modern path also falls back to legacy on plugin-install failure.
- Legacy mode keeps `${VAR}` placeholders single-quoted so Claude Code
  expands them at MCP launch time (the rc wrapper injects the values),
  rather than letting the shell expand them to empty strings at install
  time. Settings.json is backed up with a timestamp before the merge,
  and the merged JSON is validated before overwriting.
- Documented the legacy path in both READMEs and the agent-integration
  docs (EN + CN), with a pointer from the docs back to the README.

* fix(cc-plugin): highlight 'source rc' final step in installer

The script runs in a subshell (bash <(curl ...)), so it can't source
the rc back into the user's interactive shell. Make the manual
follow-up step visually unmissable with bold + color, and explain why
auto-source isn't possible in a comment.

* fix(cc-plugin): address copilot review on legacy install path

- mktemp + XXXXXX for tmp files (was $$ — predictable, symlink-race
  on shared /tmp).
- Replace sed substitution with jq walk + gsub. $plugin_dir comes from
  OPENVIKING_REPO_DIR (user-configurable) and may contain &, |, \
  which would corrupt sed. jq with --arg is byte-safe.
- Wrap the merge jq in an explicit if-branch so 'set -e' can't kill the
  script before cleanup runs. Drop the now-redundant post-validation
  jq -e (a successful jq run already guarantees valid JSON output).
- README EN/CN: clarify that the 'plugin enable --scope user' tip only
  applies on newer builds that accept --scope, removing the apparent
  contradiction with the surrounding 'older builds reject --scope'.
2026-05-06 23:50:05 +08:00
t0saki e69ae7e08f fix(cc-memory-plugin): stop silently dropping tool-heavy batches; remove MIGRATION.md (#1850)
Follow-up to #1849. Two issues surfaced after merge:

1. **Data loss on tool-heavy batches** (Copilot review on #1849).
   With tool I/O now inlined in per-turn text (4 KB cap per block), an
   ordinary multi-tool turn easily pushes formatTurnsAsText(captureTurns)
   over the 24 KB captureMaxLength. The previous code path:

       const combined = formatTurnsAsText(captureTurns);
       const decision = shouldCapture(combined);
       if (!decision.capture) {
           saveState(... allTurns.length);  // advance past these turns
           return;
       }

   silently dropped the entire batch and advanced state past the dropped
   turns, so they were unreachable on subsequent hook fires. Reproduced
   with a synthetic 4-tool turn (combined length ~32 KB > 24 KB cap).

   shouldCapture() was designed for single-user-message filtering — its
   length bounds, command/non_content/question_only checks, and keyword
   trigger requirement all misfire at the batch level:
     - JSON-shaped tool I/O can match the punctuation-only regex
     - a leading "/cmd" user turn flips the whole batch to a reject
     - a single "why?" turn tags the whole batch as question_only

   Replaced with a batch-appropriate decision: skip only empty batches,
   and in keyword mode require at least one user turn to carry a trigger
   phrase. Per-turn substance is already bounded by TOOL_BLOCK_MAX_CHARS
   during harvest, so no upper-bound batch check is needed.

2. **MIGRATION.md removed.** The file documented the old user-only-capture
   default as an intentional design choice, which the new default
   (capture both sides + tool I/O) contradicts. Rather than rewriting
   the rationale, the file is removed; nothing in the plugin or repo
   references it.
2026-05-04 19:12:23 +08:00
t0saki 5d83e66855 fix(cc-memory-plugin): default-capture assistant turns + inline tool I/O (#1849)
The plugin was silently shipping with two flaws that gutted memory extraction
quality on the main-session path:

1. **captureAssistantTurns defaulted false.** The auto-capture path filtered
   out every assistant turn unless the operator explicitly opted in, while
   the subagent-stop path always pushed both sides. Same plugin, same config
   — different sessions ended up half-empty (main) vs. full (subagent), and
   nothing in the docs flagged the divergence. Default flipped to true; the
   env var still allows opting out.

2. **Tool I/O was dropped, only tool names survived.** When an assistant
   ran WebFetch / Read / Bash, the captured turn only got a
   "[tools used: WebFetch, Read]" summary — the URL fetched, the file read,
   and the result returned were all discarded. Memory extractors saw "agent
   used a tool" without any of the substantive context. Both auto-capture
   and subagent-stop now inline "[tool: NAME] <input>" for tool_use blocks
   and "[tool result] <output>" for tool_result blocks, each truncated to
   4096 chars to bound runaway sizes.

The redundant "[tools used: ...]" suffix in pushTurnsToOv is removed since
the same info is now inline. README defaults updated.
2026-05-04 19:00:24 +08:00
t0saki 36a281a5be feat: harden MCP add_resource + cc-memory-plugin claude wrapper (#1846)
* feat(mcp): restrict add_resource to remote URLs, point local files to ov CLI

The MCP add_resource tool previously accepted any path string and passed it
straight to the resource service, which on a remote-deployed OV server would
either fail (path only exists on the client) or read server-local files —
neither of which is the intended behavior, and the latter is a security hole.

Apply the same require_remote_resource_source guard the REST router already
uses, and on rejection return a clear hint pointing the user at `ov add-resource`
for local files. Also enable enforce_public_remote_targets to match the REST
contract.

* fix(cc-memory-plugin): make claude wrapper friendlier — honor env, fix null-key bug

The shell wrapper installed by setup-helper/install.sh (and documented in
README/README_CN) had four issues:

1. Hardcoded ~/.openviking/ovcli.conf — ignored OPENVIKING_CLI_CONFIG_FILE,
   the env var the `ov` CLI itself reads (crates/ov_cli/src/config.rs:6).
   Same repo, two contradictory contracts.
2. Unconditionally overrode caller's OPENVIKING_URL / OPENVIKING_API_KEY,
   stomping on direnv/explicit exports. Should be conf-as-fallback, env-wins.
3. `jq -r '.url'` returns the literal string "null" when the key is missing,
   producing OPENVIKING_API_KEY=null and 401s downstream. Use `// empty`
   instead, matching what install.sh already does correctly elsewhere
   (line 82-83 of the same script).
4. No fallback if `jq` isn't on PATH at invocation time — silently emitted
   empty values that overrode the caller's env.

Also have install.sh resolve OVCLI_CONF via OPENVIKING_CLI_CONFIG_FILE so
re-running the installer with that env set updates the right file.

The plugin's hooks (scripts/config.mjs) already do all this correctly —
this just brings the wrapper and installer up to the same standard.

* fix(cc-memory-plugin): install at --scope user so plugin is active everywhere

`--scope local` ties plugin enablement to $REPO_DIR's .claude/settings.local.json.
The moment the user `cd`s anywhere else and runs `claude`, the plugin shows up as
disabled and they have to run `claude plugin enable …` manually — defeating the
"one-shot installer" goal.

Switch both `marketplace add` and `plugin install` to `--scope user`, and add
a defensive `claude plugin enable … --scope user` afterwards to handle Claude
Code versions where install leaves the plugin disabled. README/README_CN
updated to match (and to call out the scope=local pitfall).

* refactor(mcp): reorder add_resource hint — try ov first, install as fallback

The previous hint led with "Install: curl |bash" and put "Run: ov add-resource"
as step 3. For an LLM agent reading this error, the natural reaction is to
copy the install command verbatim — even when `ov` is already on PATH, which
is the common case for users who already have OpenViking set up.

Reorder so step 1 is just `ov add-resource <path>`, step 2 is the install
fallback only if `ov` is missing, and step 3 is the ovcli.conf step that's
explicitly tagged as remote-/multi-tenant-only. Cheaper, less noisy, and
won't push agents to re-run curl|bash unnecessarily.

* style: ruff format — drop redundant quote escapes
2026-05-04 15:57:07 +08:00
t0saki 8c01e97ee4 feat(cc-memory-plugin): persistent session and recall redesign (#1615)
## feat(cc-memory-plugin): implement persistent sessions, native MCP, and async write path

This update transitions the Claude Code memory plugin from a one-shot capture model to a persistent, per-session integration with OpenViking. It introduces native MCP support directly from the FastAPI server, significantly expands the tool surface, and optimizes performance via detached async write hooks.

### Core Engineering & Capabilities
* **Persistent Sessions:** Reworked lifecycle hooks (SessionStart, PreCompact, SessionEnd) to maintain stable session IDs across the entire Claude Code conversation.
* **Native MCP Endpoint:** Replaced the Node.js MCP subprocess with a native `/mcp` endpoint on the OpenViking server.
    * Expands to 9 specialized tools: `search`, `read`, `list`, `store`, `add_resource`, `forget`, `grep`, `glob`, and `health`.
    * Propagates identity headers (`X-OpenViking-Account`, `X-OpenViking-User`) through the MCP transport.
* **Async Write Path:** Introduced a detached worker pattern for `auto-capture`, `session-end`, and `subagent-stop` hooks. Claude Code no longer blocks on network round-trips to the OpenViking server.
* **Multi-Source Recall:** Enhanced `auto-recall` to search across memories, resources, and skills with URI-deduplication and score-based filtering.

### Configuration & Integration
* **Unified Auth:** Standardized on `Authorization: Bearer` tokens.
* **Config Resolution:** Established a clear priority chain: **Env Vars → ovcli.conf → ov.conf → Defaults**.
    * Added comprehensive environment variable coverage for all tuning fields (e.g., `OPENVIKING_SCORE_THRESHOLD`, `OPENVIKING_COMMIT_TOKEN_THRESHOLD`).
* **One-Line Installer:** Added an interactive bash installer (`install.sh`) that handles dependencies, `ovcli.conf` setup, and marketplace registration, with support for both self-hosted and Volcengine Cloud options.

### Bug Fixes & Refinement
* **Self-Injection Prevention:** Implemented block-stripping logic in `auto-capture` to prevent the plugin from re-storing its own injected context blocks into the memory pool.
* **Session Bypass:** Fixed a bug where `session-start` and `subagent-start` ignored bypass patterns.
* **Subagent Isolation:** Implemented `SubagentStart/Stop` hooks with isolated session IDs and specialized agent headers for memory segregation.
* **Docs & Maintenance:** Bumped version to `0.2.2`; added comprehensive agent integration guides; fixed Vue interpolation and markdown fence issues in documentation.
2026-05-04 13:10:44 +08:00
灿烂甜菜 e9915ec84d fix(claude-code-memory-plugin): improve Windows compatibility (#1249)
* fix(claude-code-memory-plugin): improve Windows compatibility

* docs(claude-code-memory-plugin): split marketplace link into a new paragraph
2026-04-14 08:38:49 +08:00
MaojiaShengandopenviking a7e5417ef2 reorg: remove golang depends (#1339)
* docs: fix docker deployment

* reorg: remove third_party/agfs

* feat(s3fs): add disable_batch_delete option for OSS compatibility

Port of PR #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>
2026-04-10 15:16:29 +08:00
Matt Van HornandMatt Van Horn 8019564758 fix(plugin): add skills to autoRecall search scope (#1225)
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 #1089

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-04-07 10:55:18 +08:00
灿烂甜菜 31577dae5f docs: add Claude Code Memory Plugin example link and Chinese docs (#1228)
Add README link for Claude Code Memory Plugin example in all language
variants (EN, CN, JA) and add Chinese documentation for the plugin.
2026-04-05 14:24:31 +08:00