Commit Graph
46 Commits
Author SHA1 Message Date
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
luo jiyin ddee56e0bc fix: make fake Codex launcher cross-platform (#3962) 2026-08-13 14:18:33 +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
Zayn JarvisandClaude Opus 5 4920297ccc feat(mcp): add write/edit/tree tools for viking:// as agent working directory (#3936)
* fix(storage): keep non-memory appends free of memory trailers

ContentWriteCoordinator._write_in_place routed every append through
MemoryFileUtils, which strips the existing trailing newline and appends
a reserved MEMORY_FIELDS metadata trailer, even for resource/skill files
where MEMORY_FIELDS is not a reserved format (see content_visibility).
Append to non-memory files now concatenates raw content instead, matching
POSIX append semantics and the documented visibility rules.

* feat(mcp): add write tool with exact-string edit support

Agents could not use viking:// as a working directory through MCP: no
tool could create or update file content. Add a write tool covering full
writes (mode=replace as create-or-overwrite, append, strict create) and
targeted edits (a list of {old_string, new_string, replace_all}
exact-string replacements applied in order, all-or-nothing), following
the Write/Edit conventions of common agent harnesses.

Edits read via read_visible and write back through the content-write
coordinator, so memory metadata trailers are preserved and semantic /
vector re-indexing triggers as with any other write. Parent directories
are created automatically by the storage layer. Descriptions spell out
writable scopes (resources, user memories/resources, agent) and the
wait=true knob for read-after-write search consistency.

Also update the stale tool-count comment in app.py and the MCP tool
tables in the en/zh guides (13 -> 14 tools).

* feat(mcp): add tree tool, split targeted edits into edit tool

tree renders the recursive directory tree under a viking:// URI,
indented by depth with file sizes, for whole-layout orientation;
level_limit/node_limit bound the output and include_abstract adds
per-file summaries. Missing directories report "(nothing under ...)"
instead of an error, matching the read tool's convention.

edit(uri, old_string, new_string, replace_all) takes over the targeted
exact-string replacement that previously lived in write's edits array,
matching the classic Edit tool signature harnesses already train on.
write now only does full-content writes (content + mode), removing the
mutually-exclusive content/edits schema ambiguity. Edits still read via
read_visible and write back through the content-write coordinator, so
memory metadata trailers are preserved and re-indexing triggers as with
any other write.

* test(plugin): update canonical MCP tool list for tree/write/edit

The marketplace test pins the server-registered MCP tool list; add the
new tree, write, and edit tools to fix plugin-tests CI.

* feat(storage): support plain files at the user scope root

Agents treating viking:// as a working directory naturally drop files
like viking://user/zeus-persona.md at the user root, but the write
coordinator only accepted the memories/ and resources/ subtrees.

Two changes make that work:

- Namespace shorthand: a dotted first segment under viking://user/ is a
  file name, not a user id (canonical user ids are dot-free by
  convention), so viking://user/zeus-persona.md now canonicalizes to
  viking://user/<current-user>/zeus-persona.md, matching how the
  reserved memories/resources/skills segments already shorthand.
  Dot-free segments still address an explicit user, and an exact match
  with the current user id still wins.

- Coordinator: plain files directly under the user root (or in
  non-managed subdirectories) anchor their semantic refresh at the
  parent directory. The managed subtrees skills/, peers/, privacy/ and
  sessions/ remain read-only with an actionable error message.

* fix(namespace): narrow user-root shorthand to text-file extensions

Review on #3936 (codex /review-pr) flagged that treating any dotted
segment as a user-root file shorthand would silently re-route canonical
URIs for valid dotted user ids (e.g. alice.smith) into the current
user space. Shorthand now triggers only when the first segment ends
in a common text-file extension; dotted or email-style user ids keep
resolving as canonical user ids. Adds regression tests pinning both
behaviors.

* fix(mcp): resolve user URIs against current user

* test(mcp): pin plain-file writes directly at the user root

The user-root shorthand exists so an agent can drop viking://user/persona.md
into its workspace, but every new test went through an intermediate directory
(viking://user/project/zeus-persona.md), leaving the no-directory shape — the
one that anchors the write coordinator's refresh at the user root itself —
uncovered. Add the missing case.

Also correct the write tool docstring: the create-extension allowlist applies
to any newly created file, including one created by mode="replace" falling
back to create, not only to an explicit mode="create".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 11:11:27 +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 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
huangruitengandhuangruiteng 379c19f66e fix(codex): preserve recall on Windows spawn failure (#3308)
Co-authored-by: huangruiteng <huangruiteng@bytedance.com>
2026-07-19 13:35:41 +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
huangruitengandhuangruiteng 32acc6a32f fix(codex): compress type-quota recall results (#3248)
Co-authored-by: huangruiteng <huangruiteng@bytedance.com>
2026-07-15 14:44:21 +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
t0sakiandZaynJarvis 85b9878be3 feat(pi): add OpenViking context takeover (#3081)
Co-authored-by: ZaynJarvis <31875147+ZaynJarvis@users.noreply.github.com>
2026-07-08 20:15:16 +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 2c1c8bc785 docs(codex): revert #2879 doc changes; demote marketplace to local-only (#2901)
#2879 added a Codex marketplace section to the English integration docs. Revert
those docs-only additions so docs/ matches the pre-#2879 state — no marketplace
content in the agent-integration pages.

Keep the marketplace path in the plugin README, but demote it: it is local-only
(unauthenticated http://127.0.0.1:1933), does not support authenticated or
remote/cloud servers, and is not recommended. Reorder the Quick Start so the
one-line installer is path A (recommended, supports remote/cloud) and the
marketplace install is path B (local-only, not recommended).
2026-06-30 12:10:49 +08:00
LinQiang391andLinQiang391 8708debe10 feat(codex): support upstream marketplace install (#2879)
Co-authored-by: LinQiang391 <linqiang391@users.noreply.github.com>
2026-06-30 11:22:41 +08:00
Zayn Jarvis c0d8622f44 feat[codex] use session-aware search for recall (#2729)
* use session-aware search for codex recall

* fix[codex] derive recall session id without reading state

Recall was reading plugin state via loadState + resolveOvSessionId just to
derive the OV session id, which is deterministic (cx-<safe-codex-id>).
A corrupt or missing state file could crash the UserPromptSubmit hook and
block the user from typing. Derive directly from the Codex session id so
recall never touches the filesystem.

Also drop legacy ovSessionId preservation in resolveOvSessionId: old UUID
sessions from before the cx-* scheme are no longer kept, which closes a
desync where recall (now deriving cx-*) and capture (echoing a legacy
value) would target different OV sessions. The migration window has
closed; existing legacy state files are overwritten with cx-* on the next
resolve.

- auto-recall.mjs: use deriveOvSessionId directly, drop loadState import,
  make resolveRecallSessionId sync
- session-state.mjs: resolveOvSessionId always derives cx-*
- DESIGN.md / README.md: drop 'unless legacy state has ovSessionId' wording
2026-06-22 10:27:49 +08:00
agentandZaynJarvis 3757a143f0 [codex] fix memory plugin recall and auth handling (#2676)
* fix codex memory plugin backlog handling

* [codex] fix install.sh validators: optional peer + parenthesized plugin list state

Two false-positive validations surfaced when re-running the installer
on a no-peer config after #2598 merged:

1. cached .mcp.json validator unconditionally required the
   X-OpenViking-Actor-Peer mapping. Since syncMcpConfig now omits that
   header when no peer is configured (#2598 commit 216dbb5e), the
   validator failed every no-peer install. Make it conditional, and
   symmetrically flag a stale header when no peer is configured (the
   same way bearer_token_env_var handles HAS_API_KEY both ways).

2. 'codex plugin list' validator grep expected '<id>  installed,
   enabled' but codex CLI actually prints '<id> (installed, enabled)'
   with parens. Loosen the regex to accept both forms.

- ov-credentials.mjs gains a 'has-peer-id' CLI command symmetric to
  'has-api-key', so install.sh can detect peer presence via the same
  resolver used at render time. Underlying peerId resolution is
  already covered by ov-credentials.test.mjs cases 1, 2, and 3.
- install.sh detects HAS_PEER_ID, mirrors the bearer pattern at the
  validator, and makes the codex-plugin-list regex tolerate the
  parenthesized state form.

Verified by re-running setup-helper/install.sh non-interactively
against the no-peer ovcli.conf: 'Plugin install looks valid.' now
prints with no warnings. Hand-traced both HAS_PEER_ID branches of
the new validator (peer-configured / peer-absent, header-present
/ header-absent) to confirm the four cases behave correctly.

* fix codex backfill capture parsing

* refactor codex background transcript capture

* fix: state

* fix:bug

* fix: auto capture

* fix

* fix

* Revert experimental Codex capture changes

---------

Co-authored-by: ZaynJarvis <zaynjarvis@gmail.com>
2026-06-18 12:45:06 +08:00
Zayn Jarvis c6990e4cd6 [codex] tighten memory recall, capture, and resume (#2598)
* Fix Codex memory hook recall noise and stop timeouts

* Tighten Codex recall compression output

* Add Codex archive resume and capture filtering

* Wrap Codex memory injection for capture filtering

* Detect Codex recall compressor profile

* Refresh Codex compressor profile on startup

* fix codex ov credential resolution

* [codex] resolve compressor profile via models_cache.json, not codex exec probe

SessionStart used to spawn 'codex exec' sequentially against each
candidate model to detect which one would respond — up to 3 probes ×
~15s timeout each, on every session start, even on resume. The
configured-on-startup default made this a guaranteed first-page-load
tax of several seconds.

Replace the probe with a lookup against codex's own model catalogue
(~/.codex/models_cache.json, refreshed by codex CLI's etag-backed
fetch). The first candidate whose slug is present wins. SessionStart
now goes cache-first: load the persisted profile if any, only resolve
on cache miss. The runtime compress path (auto-recall) deletes the
cached profile on any compress failure so the next SessionStart
re-resolves against the current catalogue.

- recall-compressor-profile.mjs:
  * loadCodexModelsCache(env) reads ~/.codex/models_cache.json; missing
    cache yields {present:false,slugs:Set()}.
  * resolveRecallCompressorProfile picks the first available candidate
    by slug; falls back optimistically to the first candidate when
    the catalogue is missing.
  * invalidateRecallCompressorProfileCache() rms the persisted file.
  * detectRecallCompressorProfile is now cache-first and never spawns.
- auto-recall.mjs: runCodexCompressor invalidates the cache on spawn
  error, timeout, non-zero exit, and read failure (best-effort,
  no error surface to user).
- recall-compressor-profile.test.mjs: 11 unit tests covering catalogue
  read, candidate selection (with/without configured first), missing
  catalogue fallback, configured_off path, invalidate, cache-first
  detect, and re-resolve after invalidate.

Notes:
- buildCodexExecArgs is still exported so auto-recall can spawn the
  actual compress run; the change only removes the *probe* spawn, not
  the compress spawn.
- recallCompressDetectTtlMs and recallCompressDetectTimeoutMs are
  preserved in config for back-compat; the timeout no longer matters
  but the TTL still bounds how stale a cached profile may be.

* [codex] omit X-OpenViking-Actor-Peer env_http_headers when no peer configured

syncMcpConfig used to unconditionally write all three OV header→env
mappings. The wrapper strips empty OPENVIKING_PEER_ID before exec'ing
codex, so an unset env var would silently flip the header to "" — the
OV side then has to disambiguate that from "no peer scope". Match the
bearer_token_env_var pattern: present only when there's something to
send. Also drops a stale X-OpenViking-Actor-Peer entry when the peer
is unset (e.g. after switching ovcli configs).

- Existing test 4 became two cases: with-peer keeps the mapping,
  without-peer drops it (symmetric to bearer).
- New test asserts an in-place drop when the cached .mcp.json had a
  stale peer mapping but the active config no longer has a peer.

* [codex] runtime_failed compressor marker stops same-session retry storms

Previous fix invalidated the profile cache on compress failure. Within a
single codex session that still bled `recallCompressTimeoutMs` of wall
time per UserPromptSubmit because the next hook reread cache (miss),
fell back to fallbackRecallCompressorProfile, and tried the same model.

Replace plain invalidate with a runtime_failed sentinel cached in the
profile slot. UserPromptSubmit's compressMemoryContext already short-
circuits on `profile.enabled === false`, so the marker stops further
spawns for the rest of the codex process. The next SessionStart cache-
first detect treats `source === 'runtime_failed'` as cache miss and
re-resolves against the current models_cache.json, so a transient
failure self-recovers across codex restarts without operator action.
detect_on_startup=false respects the marker (no auto-recover, matches
the "manual control" intent of that flag).

- recall-compressor-profile.mjs:
  * markRecallCompressorRuntimeFailed(cfg, {failedModel}) writes the
    disabled sentinel.
  * detectRecallCompressorProfile branches on cached.source ===
    'runtime_failed': cache hit otherwise, recover-via-resolve when
    startup-detect on, respect marker when off.
- auto-recall.mjs::runCodexCompressor: swap invalidate-on-error with
  markRecallCompressorRuntimeFailed(cfg, {failedModel: profile.model}).
- recall-compressor-profile.test.mjs: 4 new tests covering marker
  write, cross-restart recovery picking a different slug, and the
  detect_on_startup=false honor path. 20/20 pass.

invalidateRecallCompressorProfileCache is kept as a public API for
explicit operator use (e.g. a future `ov codex reset-compressor`
command), but is no longer called from the runtime path.
2026-06-16 02:05:18 +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 e06671b351 feat(session): 将 session 存储到 user 命名空间 (#2556)
* feat(session): store sessions in user namespace

* fix(session): tolerate legacy commit body fields
2026-06-11 14:33:00 +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 42484bf91d fix(plugin/codex): default-on assistant capture, recommend env vars over ov.conf for tuning (#2065)
Two related fixes to plugin tuning ergonomics:

1. `captureAssistantTurns` defaults to true (mirrors claude-code-memory-plugin).
   A memory plugin that only captures the user side of every turn extracts
   half the conversation and produces noticeably worse memories. Operators
   who want the old user-only behavior can still set
   `OPENVIKING_CAPTURE_ASSISTANT_TURNS=0` or `codex.captureAssistantTurns=false`.

2. README + agent-integrations docs (zh+en) now recommend `OPENVIKING_*`
   environment variables in shell rc as the primary way to tune the plugin.
   The previous docs claimed the tuning block lived in `ovcli.conf`, but
   `scripts/config.mjs` only reads `codex.*` from `ov.conf` — and `ov.conf`
   is server-scope, so per-machine plugin tuning doesn't belong there
   anyway. The legacy `ov.conf` path is acknowledged and kept working for
   backward compat, but de-emphasized.
2026-05-15 13:07:33 +08:00
Zayn Jarvis ace6efcbe2 fix(codex): derive ov session ids from codex sessions (#2039) 2026-05-14 19:20:05 +08:00
Zayn Jarvis a7d27920cd fix(plugin/codex): simplify commit hook messages (#2036) 2026-05-14 13:04:01 +08:00
Zayn Jarvis aa93690d2f fix(plugin/codex): raise auto-recall score threshold (#2033) 2026-05-14 12:26:27 +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 c73a32f270 fix(plugin/codex): allow empty api_key (unauthenticated local OV) (#2023)
* fix(plugin/codex): allow empty api_key (unauthenticated local OV)

Reported: with an ovcli.conf that has no `api_key` (typical local OV
without auth), the plugin would not start cleanly. Root cause: .mcp.json
ships with `bearer_token_env_var: "OPENVIKING_API_KEY"`, and when that
env var resolves to an empty string at codex launch (because ovcli.conf
has no key), Codex interprets it as "auth configured but not provided"
and falls back to its OAuth dance — which then fails against an OV that
doesn't speak OAuth.

Hook side is unaffected: scripts/config.mjs already gates the Bearer
header on `if (cfg.apiKey)`, so empty api_key → no Authorization header
sent → OV accepts in unauth mode. Verified end-to-end with auto-recall
against `http://127.0.0.1:1933` and an empty-key ovcli.conf.

Fix: at install time, detect whether ANY api_key is configured (env or
ovcli.conf) and conditionally render `.mcp.json` *with or without*
`bearer_token_env_var`:

  - api_key present → keep `bearer_token_env_var: "OPENVIKING_API_KEY"`
  - api_key absent  → drop the field entirely (Codex will then just hit
                      OV without Authorization and treat 200 as success)

Implementation uses node (already required) to read/edit the cached
.mcp.json as proper JSON rather than sed, so we don't have to worry
about field-position-dependent regexes.

Installer footer now also reports the resolved auth mode so the user
sees `MCP auth: Bearer (OPENVIKING_API_KEY)` vs `MCP auth: none
(unauthenticated)` at the end of the run.

env_http_headers stays in both modes — identity headers
(X-OpenViking-Account / User / Agent) are independent of auth and OV
accepts empty values (defaults to "default").

* fix(plugin/codex): support runtime OPENVIKING_CLI_CONFIG_FILE swap

Reported: setting OPENVIKING_CLI_CONFIG_FILE=ovcli-local.conf (a config
without api_key, for benchmark-memory isolation) and running codex fails
with:

  Environment variable OPENVIKING_API_KEY for MCP server 'openviking-memory'
  is empty

Two issues stacked on top of each other:

1. Codex 0.130 hard-fails MCP startup when bearer_token_env_var resolves
   to an EMPTY env var (confirmed empirically — not OAuth fallback, just
   a startup error).

2. The previous codex() wrapper exported `OPENVIKING_API_KEY=""` via the
   inline-prefix syntax `OPENVIKING_API_KEY="${...:-${...:-}}" codex`,
   which sets the variable to an empty string when no key is resolvable.
   So even my prior fix (don't render bearer_token_env_var when no key
   at install time) didn't help users who install with one conf and run
   with another via OPENVIKING_CLI_CONFIG_FILE.

Fix is two parts:

a) Build the env prefix dynamically into a bash array, skipping any
   OPENVIKING_* whose resolved value is empty. So an empty api_key
   produces no OPENVIKING_API_KEY at all in codex's env — neither
   set-to-empty nor set-to-something.

b) Have the wrapper re-render the cached .mcp.json's bearer_token_env_var
   on every codex launch based on the currently-active ovcli.conf. The
   idempotent fast-path skips writing when the desired state already
   matches. This makes swapping configs at runtime (typical benchmark
   isolation workflow) work without re-running the installer.

The wrapper now uses `env "${_env_args[@]}" codex "$@"` instead of the
inline-prefix form for the same reason — proper handling of conditional
env-var presence.

Manual setup snippets in README + docs (en/zh) updated to the same
empty-aware pattern; the cache-rendering bit is left to the installer-
emitted wrapper since it's noisy and only needed when actually swapping
configs.

Validated with synthetic test:

  ovcli-local.conf (no api_key)
    → env passed to codex: URL=..., ACCOUNT=..., USER=..., AGENT_ID=codex
      (no OPENVIKING_API_KEY at all)
    → cache .mcp.json rewritten to drop bearer_token_env_var

  ovcli.conf (with api_key)
    → env passed to codex: URL=..., API_KEY=..., ACCOUNT=..., USER=..., AGENT_ID=codex
    → cache .mcp.json rewritten to re-add bearer_token_env_var

  Idempotent: re-render with same hasKey state does not bump file mtime.

* fix(plugin/codex): wrapper also re-renders cache .mcp.json URL

Previously the codex() wrapper only re-rendered bearer_token_env_var
based on the active ovcli.conf, but the cached .mcp.json URL stayed
whatever was baked at install time. Result: swapping
OPENVIKING_CLI_CONFIG_FILE to a config that points at a different OV
server (e.g. localhost) would still hit the install-time URL —
typically the remote production OV — and fail auth.

Reported in testing:

  OPENVIKING_CLI_CONFIG_FILE=ovcli-local.conf codex
  # ovcli-local.conf: { "url": "http://127.0.0.1:1933" }
  # cache .mcp.json still says url=https://ov-dev.tosaki.top/mcp
  # Codex hits remote ov-dev with no bearer → 401 → "Not logged in" OAuth dance

Fix: the rewrite block now also patches s.url from the conf-resolved
URL (`${_ov_url%/}/mcp`, or `$OPENVIKING_MCP_URL` if explicitly set).
Same idempotent fast-path — only writes when something actually changed.

Tested both directions:
  ovcli-local.conf (no key, localhost)
    → cache .mcp.json: url=http://127.0.0.1:1933/mcp, no bearer field
    → env passed to codex: no OPENVIKING_API_KEY
    → /mcp: Auth: None, tools list populated
  ovcli.conf (with key, remote)
    → cache .mcp.json: url=https://ov-dev.tosaki.top/mcp, bearer present
    → /mcp: Auth: Bearer token, tools list populated
2026-05-13 21:40:30 +08:00
t0saki b0076110ae refactor(plugin/codex): switch MCP from local stdio to OV /mcp directly (#2022)
* refactor(plugin/codex): switch MCP from local stdio server to OV /mcp (http)

Codex 0.130 supports streamable-HTTP MCP servers with bearer auth via
`bearer_token_env_var` in `.mcp.json` (and per-header env binding via
`env_http_headers`). OpenViking server has exposed `/mcp` natively since
1.27, so the local stdio MCP middleman (`src/memory-server.ts` +
`servers/memory-server.js` + the npm-ci runtime bootstrap) is dead weight:
the model now gets a strictly larger tool set (search, store, read, list,
grep, glob, forget, add_resource, health — vs the previous recall/store/
forget/health) by talking to OV directly, and the plugin loses its only
build/dependency surface.

What changed

- `.mcp.json`: switched to `url` + `bearer_token_env_var: "OPENVIKING_API_KEY"`
  + `env_http_headers` for the multi-tenant identity headers. URL is a
  `__OPENVIKING_MCP_URL__` placeholder; installer renders it from ovcli.conf
  / `OPENVIKING_URL` at install time. API key never lands on disk in the
  cached .mcp.json — it's pulled from process env at codex launch.

- `setup-helper/install.sh`: resolves the OV /mcp URL (OPENVIKING_MCP_URL >
  OPENVIKING_URL/mcp > ovcli.conf.url/mcp > localhost), renders the
  .mcp.json placeholder into the cached copy, and appends a `codex()` shell
  function wrapper to the user's rc that promotes ovcli.conf fields into
  env vars before exec'ing codex (mirrors the claude-code-memory-plugin
  pattern; needed because Codex reads OPENVIKING_API_KEY from process env
  at MCP launch, not from any file).

- Deleted: `src/memory-server.ts`, `servers/memory-server.js`, `tsconfig.json`,
  `package.json`, `package-lock.json`, `scripts/bootstrap-runtime.mjs`,
  `scripts/runtime-common.mjs`, `scripts/start-memory-server.mjs`. Net
  ~2400 lines removed. Hook scripts remain zero-dep .mjs running on
  Codex's bundled Node 22.

- README + docs/{en,zh}/agent-integrations/04-codex.md: rewritten to
  describe the new architecture. The MCP tools list and protocol details
  are now referenced via a link to docs/{en,zh}/guides/06-mcp-integration.md
  rather than duplicated in the plugin docs.

- Plugin version: 0.4.1 → 0.5.0.

Validation

Verified end-to-end on Codex 0.130 against `ov-dev.tosaki.top`:

  /mcp
  🔌 MCP Tools
    • openviking-memory
      • Auth: Bearer token
      • Tools: add_resource, forget, glob, grep, health, list, read, search, store

`openviking-memory.health` returned `OpenViking is healthy ... storage: VikingFS`;
Stop hook reported `appended 2 turn(s) to OpenViking session <id>`.

Notes

- `.mcp.json` headers that don't have a corresponding env var (e.g. user
  didn't set `OPENVIKING_USER`) are simply not sent — `env_http_headers`
  silently omits missing vars per Codex's MCP runtime.
- Rotating the API key now just needs `codex` restart (env re-reads from
  ovcli.conf via the wrapper). URL changes still need a re-install since
  the URL is baked into the cached .mcp.json.
- The shell function wrapper has a marker-delimited block so re-running
  the installer replaces it in place rather than appending duplicates.

* review(plugin/codex): address copilot feedback on installer + docs

1. Switch the codex() shell-function wrapper from jq to node. The installer
   already hard-requires node 22+, while jq is not always present; the old
   wrapper would silently fall through to `command codex` with no env
   injection when jq was missing, which caused Codex to start with no
   Bearer token, OV to return 401, and Codex to drop into its OAuth
   fallback. Now there is a single tool dependency for both the installer
   and the wrapper it emits.

2. Marker-replacement is now defensive: rewrite-in-place only triggers
   when BOTH the BEGIN and END markers exist in the rc. If only BEGIN
   is present (manual edit / corruption), warn and append a fresh block
   instead of awk-dropping everything from BEGIN to EOF.

3. When no rc is detected, omit the `source $RC` line from the final
   "Next:" hint and tell the user to paste the snippet manually instead
   of printing `source ` with a trailing space.

4. Docs (README + 04-codex.md zh/en): use the full env var names
   (OPENVIKING_API_KEY / OPENVIKING_ACCOUNT / OPENVIKING_USER /
   OPENVIKING_AGENT_ID) instead of `_ACCOUNT` / `_USER` shorthand;
   update the manual-setup snippets to the node-based wrapper.

The wrapper body is now defined once and reused for both the appended-to-rc
path and the manual-paste path, so the two cannot drift.
2026-05-13 21:08:57 +08:00
t0saki 8034abc158 docs(plugin/codex): dedicated agent-integrations page (zh+en) + fix MCP startup (#2019)
* docs(plugin/codex): add dedicated agent-integrations page + fix MCP startup

Follow-up to #1957. Lifts Codex out of `04-other-plugins.md` into its own
`04-codex.md` (en + zh) with full install steps, configuration, hook
behavior, and troubleshooting — mirrors the shape of `02-claude-code.md`.

Renumbers `04-other-plugins.md` → `05-` and `05-langchain-langgraph.md`
→ `06-`. Overview tables in both locales updated; cross-refs fixed.

Also fixes two install/runtime bugs surfaced while validating the fresh
installer flow against the merged PR:

1. **Stale repo clone**: `setup-helper/install.sh` previously skipped the
   clone if `~/.openviking/openviking-repo` already existed, so a user
   who installed before #1957 merged ended up with a pre-PR plugin
   checkout (no `scripts/`, no `servers/memory-server.js`). The installer
   now `git fetch + reset --hard` an existing checkout to `$REPO_REF`
   (default `main`), matching the claude-code installer pattern.

2. **`${CODEX_PLUGIN_ROOT}` not expanded in `.mcp.json`**: Codex 0.130
   does not substitute env vars in `.mcp.json` `args`/`env` and does not
   always inject `CODEX_PLUGIN_ROOT` into MCP child env. The literal
   string `${CODEX_PLUGIN_ROOT}` was being passed to node, which then
   tried to resolve `${CODEX_PLUGIN_ROOT}/scripts/start-memory-server.mjs`
   against codex's cwd and failed with `MODULE_NOT_FOUND`. Fix:
   - `.mcp.json`: `args: ["scripts/start-memory-server.mjs"]` + `cwd: "."`
     (matches the syntax 0.1.0 used, which Codex does honor)
   - `scripts/runtime-common.mjs`: derive plugin root from
     `import.meta.url` as a fallback so the launcher works regardless of
     whether `CODEX_PLUGIN_ROOT` is set in the spawn env

Bumps plugin to 0.4.1 (package.json + plugin.json + lockfile) since the
runtime-common.mjs change invalidates the install-state hash and forces
a re-install of node_modules into the per-user runtime data root.

* fix(plugin/codex): hooks.json must use relative paths, not ${CODEX_PLUGIN_ROOT}

Same root cause as the .mcp.json fix in the previous commit: Codex 0.130
does not expand ${CODEX_PLUGIN_ROOT} in hooks.json `command` strings. The
shell that runs the hook sees the literal ${CODEX_PLUGIN_ROOT} and expands
it to "" (or leaves it literal), so node tries to load `/scripts/...mjs`
and exits 1.

Symptom in the chat UI:
  • SessionStart hook (failed)  error: hook exited with code 1
  • UserPromptSubmit hook (failed)
  • Stop hook (failed)

Fix: use `./scripts/<name>.mjs` paths, matching the pattern Codex's own
bundled plugins (e.g. figma) use. Codex's hook dispatcher resolves these
relative to the plugin root (where hooks.json lives).

The MCP launcher fix from the prior commit already handles the same class
of bug for .mcp.json; this catches the hooks path.

* fix(plugin/codex): hooks.json needs absolute paths rendered at install time

Previous fix (relative ./scripts/...) was based on the figma example but
empirically does not work on Codex 0.130: the hook subprocess runs with
cwd = user's cwd (not plugin root) and CODEX_PLUGIN_ROOT is NOT injected
into the env. So both ${CODEX_PLUGIN_ROOT}/scripts/foo.mjs and
./scripts/foo.mjs resolve to the wrong absolute path and node exits 1.

Verified with a probe shell script wired into hooks.json:
  argv: /tmp/codex-hook-probe.sh SessionStart
  cwd: /Users/<user>
  CODEX_PLUGIN_ROOT: <unset>
  CODEX_PLUGIN_DATA: <unset>

(The "Under-development features are incomplete" banner Codex prints when
plugin_hooks is enabled is real - the hook env wiring is unfinished in
0.130.)

Fix: keep the source hooks.json as a template (uses __OPENVIKING_PLUGIN_ROOT__
placeholder) and have install.sh sed-render the cache copy with the
absolute $CACHE_DIR path on every install. The cached hooks.json is now
fully self-contained absolute-path commands; the repo's checked-in copy
stays portable.

.mcp.json is unaffected: Codex 0.130 does honor the `cwd: "."` field for
MCP servers, so relative args resolve against plugin root there.

* fix(plugin/codex): bump UserPromptSubmit timeout to 15s

Empirically the auto-recall hook can take 0.8s–4s end-to-end (depending on
result count and remote OV latency), and Codex 0.130 sometimes adds 4-5s
of spawn overhead before our script even starts. The original 8s budget
was borderline and produced spurious "hook timed out after 8s" UI errors
on slow paths even when the recall would have succeeded.

15s matches the auto-recall internal timeoutMs default (config.mjs:186)
and gives enough headroom for spawn-time variance without holding the
user's input noticeably longer in the worst case.

* fix(plugin/codex): installer accepts OPENVIKING_REPO_BRANCH as alias

Per review feedback: the claude-code installer uses OPENVIKING_REPO_BRANCH
for the same purpose. Aliasing both names lets users reuse one env var
across installers without remembering which plugin uses which name.

Precedence: OPENVIKING_REPO_REF > OPENVIKING_REPO_BRANCH > "main".
2026-05-13 20:33:36 +08:00
e92180a7e1 feat(plugin/codex): add lifecycle hooks (recall, capture, pre-compact) to codex-memory-plugin (#1957)
* feat(plugin/codex): add lifecycle hooks (recall, capture, pre-compact)

Brings the codex-memory-plugin to feature parity with the claude-code-memory-plugin
by wiring the four Codex lifecycle hooks via `hooks.json`:

- SessionStart  -> bootstrap-runtime.mjs (npm ci into ${CODEX_PLUGIN_DATA}/runtime)
- UserPromptSubmit -> auto-recall.mjs (search OV, inject via hookSpecificOutput.additionalContext)
- Stop -> auto-capture.mjs (incremental transcript capture + last_assistant_message commit)
- PreCompact -> pre-compact-capture.mjs (full transcript -> single OV session -> commit)

Differences from the Claude Code plugin baked into the scripts:

- Codex output schema does not allow `decision: "approve"`; no-op is `{}`
- Stop/PreCompact only support `systemMessage`, not `additionalContext`
- Plugin envs are CODEX_PLUGIN_ROOT / CODEX_PLUGIN_DATA
- Config section is `codex` (was `claude_code`); config file defaults to
  `~/.openviking/ovcli.conf`, falling back to legacy `~/.openviking/ov.conf`

Other changes:

- src/memory-server.ts now reads ovcli.conf-style configs (top-level `url`,
  `api_key`, `account`, `user`, `agent_id`) so the plugin works against
  hosted OpenViking deployments out of the box. Env-var-only operation
  (OPENVIKING_URL set, no config file) is also supported.
- .mcp.json points at scripts/start-memory-server.mjs, which boots the same
  runtime the hooks use, so the MCP path benefits from npm-ci bootstrap.
- README rewritten with architecture diagram, validation SOP, configuration
  reference, and a Codex-vs-Claude-Code differences table.

Validated end-to-end against an OpenViking deployment:

- Auto-recall returns ranked memories with full content and emits
  hookSpecificOutput.additionalContext.
- Auto-capture (last_assistant_message path) creates a session, commits, and
  the OV pipeline extracts events + preferences within ~60s.
- Pre-compact-capture posts a full 4-turn transcript to one OV session,
  commits with archived=true, and produces structured leaf memories
  (preferences, events, entities) under viking://user/<user>/memories/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(plugin/codex): drop SessionStart, split Stop=add_message vs PreCompact=commit

Codex's `Stop` hook fires per turn, not at session end, so committing per-Stop
over-fragments memory extraction. And codex re-fires `SessionStart` on short
reconnects, so registering an `npm ci` bootstrap there reinstalls the runtime
unnecessarily.

This change keeps one long-lived OpenViking session per codex `session_id`
across all `Stop` invocations, and only triggers the OV memory extractor on
`PreCompact` (or via an idle-sweep best-effort commit when codex exits without
compacting).

- hooks.json: drop SessionStart entry; keep UserPromptSubmit/Stop/PreCompact
- scripts/session-state.mjs (new): per-codex-session state under
  ~/.openviking/codex-plugin-state/, tracks ovSessionId + capturedTurnCount
- scripts/auto-capture.mjs (Stop): incremental add_message only, idle-sweep at
  the tail to commit stale codex sessions (default IDLE_TTL=30 min, override
  with OPENVIKING_CODEX_IDLE_TTL_MS)
- scripts/pre-compact-capture.mjs (PreCompact): catch-up append + commit the
  long-lived OV session, then null out ovSessionId so the next Stop opens a
  fresh OV session for the post-compact half
- MCP runtime install stays lazy in start-memory-server.mjs (already there);
  no SessionStart hook means short reconnects don't re-trigger npm ci
- VERIFICATION.md: end-to-end SOP against a live OV server (~3 min)
- bump plugin to 0.3.0

Verified end-to-end against ov.zaynjarvis.com:
  Stop adds turns idempotently and incrementally; PreCompact commits to
  history/archive_001/ with extractor producing memories under
  viking://user/<user>/memories/profile.md after ~30 s; post-compact Stop
  opens a fresh OV session; idle-sweep commits stale state files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(plugin/codex): replace idle-sweep with SessionStart(source=clear) commit

Per Zayn's followup ("非必要不要加 idle commit"): drop the idle-sweep added
in the previous commit and use codex's actual context-disappearing signal —
SessionStart with source=clear — to commit orphaned sessions.

Codex hook signal map:
- /compact         → PreCompact      ✅ commit (already)
- /clear           → SessionStart(source=clear) for the NEW session_id;
                     the prior transcript is orphaned. Now committed.
- /new             → SessionStart(source=startup); ambiguous with fresh
                     codex startup, so we don't act on it.
- /resume / short reconnect → SessionStart(source=resume|startup); no-op
                     to avoid corrupting still-active sessions.
- SIGTERM/Ctrl+C/exit → no hook fires. Documented as a known gap; users
                     should /compact before /exit if they want commit.

Changes:
- new scripts/session-start-commit.mjs: gates internally on source=clear,
  iterates listStates(), and commits any state file whose codexSessionId
  != the new SessionStart session_id, then clears that state file
- hooks/hooks.json: re-register SessionStart pointing at the new script
  (timeout 30s)
- scripts/auto-capture.mjs: remove sweepIdleSessions() and
  IDLE_TTL_MS env handling; Stop is now strictly add_message
- README/VERIFICATION.md: update arch diagram, replace idle-sweep step
  with SessionStart(source=clear) verify (positive + negative paths),
  add "Known gap: SIGTERM/exit are silent" section
- bump to 0.3.1

Verified end-to-end against ov.zaynjarvis.com:
  Stop add+idempotent ✓
  SessionStart source=startup → {} ✓
  SessionStart source=resume → {} ✓
  SessionStart source=clear → committed prior OV session, history/archive_001/
  appeared, profile.md gained "Favorite snack: dark chocolate" within 30 s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(plugin/codex): SessionStart matcher = "clear" (native dispatcher gate)

Codex's hooks dispatcher matches the SessionStart hook's `matcher` field
against the SessionStart `source` value. Setting matcher to "clear" means
codex won't even spawn our script on `source=startup` or `source=resume`
(short reconnects); we previously gated this in-script. The internal
source check in session-start-commit.mjs is kept as defense-in-depth.

Source: codex-rs/hooks/src/events/session_start.rs `select_handlers(...,
matcher_input: Some(request.source.as_str()))` and
codex-rs/hooks/src/events/common.rs `is_exact_matcher` — "clear" is
all-alphanumeric so it's matched as exact equality, not regex.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(plugin/codex): active-window heuristic + idle-TTL sweep at SessionStart (v0.4.0)

Source of truth: examples/codex-memory-plugin/DESIGN.md (added in this commit).

Behavioral changes:

- SessionStart matcher widens from `clear` to `clear|startup`. Both sources
  run the same active-window heuristic; `resume` is a hard no-op (still fires
  on short reconnects).
- Heuristic (DESIGN.md §3): count state files (excluding new session_id) within
  ACTIVE_WINDOW_MS (default 2 min). 0 → noop, 1 → commit it (just-ended
  session), ≥2 → skip and rely on idle TTL. Tunable via
  OPENVIKING_CODEX_ACTIVE_WINDOW_MS.
- Idle-TTL sweep returns at the tail of session-start-commit.mjs only (not
  every Stop). Default IDLE_TTL_MS = 30 min via OPENVIKING_CODEX_IDLE_TTL_MS.
  Catches SIGTERM/Ctrl+C/`/exit` orphans and the ≥2-active skip path.
- Stop hook deliberately does NOT sweep — state-write-on-every-turn already
  gives us the freshness signal. Marker comment added.
- Stop hook adds post-compact transcript-shrink defense: if
  allTurns.length < state.capturedTurnCount, reset capturedTurnCount = 0.
- Commit-on-failure preserves state everywhere (PreCompact, heuristic,
  idle sweep). A non-2xx /commit no longer clears ovSessionId; the next
  sweep retries.
- session-state.mjs saveState now uses atomic write (tmpfile + rename) for
  crash safety. listStates ignores the brief `<id>.json.tmp` window.

Bump: package.json + .codex-plugin/plugin.json → 0.4.0.

Docs: README "How It Works" gained a DESIGN.md pointer and rewrites the
SessionStart section to reflect heuristic + idle TTL. VERIFICATION.md step 6
now exercises all four heuristic branches (0/1/≥2 active, idle TTL, resume).

Phase-2 resume context inject documented in DESIGN.md but explicitly out of
scope here.

Verified locally with synthetic stdin tests against a fake OV server:
1-active commit, ≥2-active skip, idle TTL sweep, resume noop,
unreachable-server keeps state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(plugin/codex): align config loading with claude-code plugin

Addresses three review points on PR #1957:

1. Honor OPENVIKING_CLI_CONFIG_FILE for the ovcli.conf override path
   (matches the convention used by `ov` CLI and claude-code-memory-plugin).
   OPENVIKING_CONFIG_FILE stays as the ov.conf override; for backward
   compat it still works when pointed at an ovcli-shaped file.

2. Strict env-first priority for every connection / identity field
   (baseUrl, apiKey, account, user, agentId). Env vars now win over
   ovcli.conf, which wins over ov.conf's codex.* block / server.*,
   which wins over built-in defaults.

3. Unify hook and MCP-server config loading: src/memory-server.ts now
   imports loadConfig from scripts/config.mjs (relative path stays
   valid post-compile because servers/ and scripts/ are siblings),
   eliminating the divergent account/user/agentId fallback chains
   the PR-Agent reviewer flagged.

Auth header: emit Authorization: Bearer (primary, required by OpenViking
Cloud) plus the legacy X-API-Key during the transition window. All six
fetch sites updated (4 hook scripts + memory-server.ts + compiled
servers/memory-server.js).

README: document the new resolution chain, OPENVIKING_CLI_CONFIG_FILE,
OPENVIKING_BEARER_TOKEN alias, and the Authorization: Bearer migration.

* docs(plugin/codex): put installation first

* fix(plugin/codex): harden runtime and capture paths

* docs(plugin/codex): align local marketplace name

* docs(plugin/codex): add one-line installer

* fix(plugin/codex): support branch installer testing

* fix(plugin/codex): keep installer env surface stable

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: zhengxiao.wu <zhengxiao.wu@bytedance.com>
2026-05-13 19:25:30 +08:00
a5649a4d20 chore(agent-tools): converge MCP tool names (#1851)
* chore(agent-tools): converge MCP tool names

Rename model-visible explicit memory tools without adding a new agent HTTP API or changing existing CLI behavior.

Keep OV server /mcp store renamed to remember while preserving its existing session write and commit implementation.

Rename Codex MCP openviking_store to remember and keep its original session create/message/commit/cleanup flow; leave OpenClaw memory_store and ov add-memory unchanged.

Co-authored-by: GPT-5.5 <noreply@openai.com>

* refactor(agent-tools): align MCP and search tool names

Apply the search-tool alignment patch across Codex MCP, OpenClaw, OV MCP docs, and focused tests.

Co-authored-by: wlff123 <wulf234@163.com>

Co-authored-by: GPT-5.5 <noreply@openai.com>

---------

Co-authored-by: GPT-5.5 <noreply@openai.com>
2026-05-08 10:50:20 +08:00
Brian Le 17dea04f61 feat(examples): add Codex memory plugin example (#1080)
* feat(examples): add Codex memory plugin example

* fix(codex-memory-plugin): wait for delete consistency

Wait for memory_forget deletions to settle before reporting success so the Codex adapter does not claim a delete while content/read can still see the memory in the same context.

* docs(examples): add Codex plugin QA evidence

* feat(examples): make Codex memory plugin hook-first

* fix(api): harden filesystem content checks

* fix(tests): use concrete fake embedder in quick start lite

* refactor(examples): simplify codex memory plugin

* fix(examples): tighten Codex MCP config

* test(examples): guard fake embedder dimensions

* fix(examples): route Codex memory MCP by configured user
2026-04-12 13:09:25 +08:00