From 6a6b023f951b8847895ab49268ae52edf83f57b9 Mon Sep 17 00:00:00 2001 From: ZaynJarvis Date: Wed, 13 May 2026 20:31:10 +0800 Subject: [PATCH] fix(plugin/codex): use native OpenViking MCP --- .../en/agent-integrations/04-other-plugins.md | 29 +- .../zh/agent-integrations/04-other-plugins.md | 25 +- .../.codex-plugin/plugin.json | 6 +- examples/codex-memory-plugin/.gitignore | 4 - examples/codex-memory-plugin/.mcp.json | 13 - examples/codex-memory-plugin/DESIGN.md | 8 +- examples/codex-memory-plugin/README.md | 137 +- examples/codex-memory-plugin/VERIFICATION.md | 26 +- .../codex-memory-plugin/package-lock.json | 1174 ----------------- examples/codex-memory-plugin/package.json | 17 +- .../scripts/bootstrap-runtime.mjs | 39 - .../codex-memory-plugin/scripts/config.mjs | 8 +- .../scripts/runtime-common.mjs | 284 ---- .../scripts/start-memory-server.mjs | 54 - .../servers/memory-server.js | 274 ---- .../setup-helper/install.sh | 188 ++- .../codex-memory-plugin/src/memory-server.ts | 374 ------ examples/codex-memory-plugin/tsconfig.json | 15 - 18 files changed, 324 insertions(+), 2351 deletions(-) delete mode 100644 examples/codex-memory-plugin/.mcp.json delete mode 100644 examples/codex-memory-plugin/package-lock.json delete mode 100644 examples/codex-memory-plugin/scripts/bootstrap-runtime.mjs delete mode 100644 examples/codex-memory-plugin/scripts/runtime-common.mjs delete mode 100644 examples/codex-memory-plugin/scripts/start-memory-server.mjs delete mode 100644 examples/codex-memory-plugin/servers/memory-server.js delete mode 100644 examples/codex-memory-plugin/src/memory-server.ts delete mode 100644 examples/codex-memory-plugin/tsconfig.json diff --git a/docs/en/agent-integrations/04-other-plugins.md b/docs/en/agent-integrations/04-other-plugins.md index 585c7300a7..702a0587c8 100644 --- a/docs/en/agent-integrations/04-other-plugins.md +++ b/docs/en/agent-integrations/04-other-plugins.md @@ -6,7 +6,7 @@ The repo also ships several community/experimental plugins beyond the headline C Source: [examples/codex-memory-plugin](https://github.com/volcengine/OpenViking/tree/main/examples/codex-memory-plugin) -[Codex](https://github.com/openai/codex) integration with lifecycle hooks and explicit MCP tools. It follows the same install-first shape as the [Claude Code integration](./02-claude-code.md), but uses Codex hook events. +[Codex](https://github.com/openai/codex) integration with lifecycle hooks plus OpenViking's native `/mcp` endpoint for explicit tools. It follows the same install-first shape as the [Claude Code integration](./02-claude-code.md), but uses Codex hook events. ### Install @@ -16,7 +16,9 @@ Recommended one-line installer: bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/codex-memory-plugin/setup-helper/install.sh) ``` -It installs from a local `openviking-plugins-local` marketplace, enables `openviking-memory@openviking-plugins-local`, sets `features.plugin_hooks = true`, and uses `~/.openviking/ovcli.conf` for the OpenViking connection when present. +It installs from a local `openviking-plugins-local` marketplace, enables `openviking-memory@openviking-plugins-local`, sets `features.plugin_hooks = true`, optionally registers `mcp_servers.openviking` against the OpenViking server's native `/mcp` endpoint, and uses `~/.openviking/ovcli.conf` for the OpenViking connection when present. + +Native MCP is recommended but optional. In an interactive shell the installer asks whether to enable it; set `OPENVIKING_CODEX_ENABLE_MCP=0` for a hooks-only install, which removes the installer-managed `mcp_servers.openviking` entry if one exists. Manual setup: @@ -55,6 +57,21 @@ enabled = true EOF ``` +Optional native MCP registration: + +```toml +[mcp_servers.openviking] +url = "https://ov.example.com/mcp" +bearer_token_env_var = "OPENVIKING_API_KEY" +startup_timeout_sec = 30 +tool_timeout_sec = 120 + +[mcp_servers.openviking.http_headers] +"X-OpenViking-Account" = "default" +"X-OpenViking-User" = "" +"X-OpenViking-Agent" = "codex" +``` + For local development, pre-populate Codex's cache so it resolves immediately: ```bash @@ -63,7 +80,7 @@ mkdir -p "$INSTALL_DIR" cp -R "$(pwd)/examples/codex-memory-plugin" "$INSTALL_DIR/0.4.0" ``` -`npm install && npm run build` is only required when editing the TypeScript MCP server source; the checked-in plugin already includes `servers/memory-server.js`. +No npm install or build step is required; the plugin scripts are plain Node.js modules and explicit MCP tools come from OpenViking's native `/mcp` endpoint. ### Configure @@ -78,7 +95,9 @@ Use `~/.openviking/ovcli.conf`, shared with the `ov` CLI: } ``` -Environment variables win over files. Use `OPENVIKING_CLI_CONFIG_FILE` for an alternate `ovcli.conf`; `OPENVIKING_API_KEY` and `OPENVIKING_BEARER_TOKEN` are equivalent. +Environment variables win over files for hooks. Use `OPENVIKING_CLI_CONFIG_FILE` for an alternate `ovcli.conf`; `OPENVIKING_API_KEY` and `OPENVIKING_BEARER_TOKEN` are equivalent. + +Codex's native HTTP MCP transport does not read `ovcli.conf`, so the installer writes a literal `/mcp` URL and header block to `~/.codex/config.toml`. Keep the configured bearer env var, usually `OPENVIKING_API_KEY`, set when starting Codex. ### What it does @@ -86,7 +105,7 @@ Environment variables win over files. Use `OPENVIKING_CLI_CONFIG_FILE` for an al - Incremental capture on `Stop` - Commit before compaction on `PreCompact` - Orphan cleanup on `SessionStart` startup/clear -- Manual MCP tools: `openviking_recall`, `openviking_store`, `openviking_forget`, `openviking_health` +- Native OpenViking MCP tools such as `search`, `read`, `list`, `store`, `add_resource`, `grep`, `glob`, `forget`, and `health` Full behavior and validation details are in the [plugin README](https://github.com/volcengine/OpenViking/tree/main/examples/codex-memory-plugin). diff --git a/docs/zh/agent-integrations/04-other-plugins.md b/docs/zh/agent-integrations/04-other-plugins.md index 5b67a42b60..2a651b8cab 100644 --- a/docs/zh/agent-integrations/04-other-plugins.md +++ b/docs/zh/agent-integrations/04-other-plugins.md @@ -2,20 +2,29 @@ 仓库里还附带了几个未在 Claude Code 和 OpenClaw 主集成中介绍的社区/实验性插件。它们在目标 runtime、集成深度和维护状态上各有差异,使用前请先阅读各自的 README。 -## Codex 记忆 MCP Server +## Codex 记忆插件 源码:[examples/codex-memory-plugin](https://github.com/volcengine/OpenViking/tree/main/examples/codex-memory-plugin) -面向 [Codex](https://github.com/openai/codex) 的最小化 MCP-only 服务,刻意保持窄边界: +面向 [Codex](https://github.com/openai/codex) 的生命周期 hook 集成,并把显式工具接到 OpenViking server 原生 `/mcp`: -- 不挂生命周期 hook -- 不跑后台捕获 worker -- 不写 `~/.codex` -- 不留 build 产物 +- `UserPromptSubmit` 自动召回并注入相关记忆 +- `Stop` 增量追加对话 turn 到同一个 OpenViking session +- `PreCompact` 在 Codex 压缩上下文前提交 session,触发记忆抽取 +- `SessionStart(startup|clear)` 用 active-window heuristic 和 idle-TTL sweep 清理 orphan session +- 显式工具不再由插件自带 stdio server 提供,而是统一走 OpenViking server 的 `/mcp` -Codex 拿到的只是几个显式记忆工具:`find`、`remember`,外加几个辅助。 +推荐安装: -如果你只需要 Codex 显式调用记忆(不需要自动召回/捕获),这是最简方案。 +```bash +bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/codex-memory-plugin/setup-helper/install.sh) +``` + +安装器会启用本地 `openviking-memory@openviking-plugins-local` 插件、打开 `features.plugin_hooks = true`,并可选地在 `~/.codex/config.toml` 写入 `mcp_servers.openviking`,指向 OpenViking server 的原生 `/mcp`。交互式运行时会询问是否启用 MCP;如果只想装 hooks,可设置 `OPENVIKING_CODEX_ENABLE_MCP=0`,已有的安装器管理的 `mcp_servers.openviking` 会被移除。 + +hook 会读取 `~/.openviking/ovcli.conf`;但 Codex 的 HTTP MCP transport 不读这个文件,所以 MCP URL 和 account/user/agent header 需要落到 Codex config 里。 + +原生 `/mcp` 暴露的工具包括 `search`、`read`、`list`、`store`、`add_resource`、`grep`、`glob`、`forget`、`health`(具体以 server 版本为准)。 ## OpenCode 插件 diff --git a/examples/codex-memory-plugin/.codex-plugin/plugin.json b/examples/codex-memory-plugin/.codex-plugin/plugin.json index 4a1453bfb1..985bf927be 100644 --- a/examples/codex-memory-plugin/.codex-plugin/plugin.json +++ b/examples/codex-memory-plugin/.codex-plugin/plugin.json @@ -17,18 +17,16 @@ "codex" ], "hooks": "./hooks/hooks.json", - "mcpServers": "./.mcp.json", "interface": { "displayName": "OpenViking Memory", "shortDescription": "Long-term semantic memory for Codex", - "longDescription": "Hooks Codex's lifecycle to keep an external semantic memory: recall relevant memories on each UserPromptSubmit; on every Stop (turn end) append the new user/assistant turns to a long-lived OpenViking session keyed by codex session_id; on PreCompact commit that session so OV's extractor produces durable memories before context is summarized; on SessionStart(source=clear) commit any orphaned prior session before /clear discards it. Also exposes explicit MCP tools (openviking_recall, openviking_store, openviking_forget, openviking_health) for manual use.", + "longDescription": "Hooks Codex's lifecycle to keep an external semantic memory: recall relevant memories on each UserPromptSubmit; on every Stop (turn end) append new transcript turns to a long-lived OpenViking session keyed by codex session_id; on PreCompact commit that session so OV's extractor produces durable memories before context is summarized; on SessionStart(source=startup|clear) commit orphaned prior sessions with the active-window heuristic and idle-TTL sweep. The installer also registers OpenViking's native /mcp endpoint for explicit tools such as search, read, store, add_resource, grep, glob, forget, and health.", "developerName": "OpenViking", "category": "Productivity", "capabilities": [ "Memory recall", "Auto memory capture", - "Manual memory storage", - "Manual memory deletion" + "Native OpenViking MCP tools" ], "websiteURL": "https://github.com/volcengine/OpenViking" } diff --git a/examples/codex-memory-plugin/.gitignore b/examples/codex-memory-plugin/.gitignore index d74551f997..c2658d7d1b 100644 --- a/examples/codex-memory-plugin/.gitignore +++ b/examples/codex-memory-plugin/.gitignore @@ -1,5 +1 @@ node_modules/ -servers/*.mjs -servers/*.js -!servers/memory-server.js -*.tsbuildinfo diff --git a/examples/codex-memory-plugin/.mcp.json b/examples/codex-memory-plugin/.mcp.json deleted file mode 100644 index 46f9ef5a6f..0000000000 --- a/examples/codex-memory-plugin/.mcp.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mcpServers": { - "openviking-memory": { - "command": "node", - "args": [ - "${CODEX_PLUGIN_ROOT}/scripts/start-memory-server.mjs" - ], - "env": { - "OPENVIKING_CONFIG_FILE": "${OPENVIKING_CONFIG_FILE}" - } - } - } -} diff --git a/examples/codex-memory-plugin/DESIGN.md b/examples/codex-memory-plugin/DESIGN.md index 6ba56ebc26..f312aebdf6 100644 --- a/examples/codex-memory-plugin/DESIGN.md +++ b/examples/codex-memory-plugin/DESIGN.md @@ -118,8 +118,8 @@ arbitrarily-orphaned state files accumulate. **Known limitation**: if the user never starts another codex on this machine, no sweep ever runs and the OV session stays open server-side -forever. Accepted. Future work could add an MCP tool -`openviking_commit_pending` so the model can commit explicitly. +forever. Accepted. Future work could add a native server MCP tool to commit +pending Codex sessions explicitly. ## Stop hook — append only, no commit @@ -223,8 +223,8 @@ proven first. ## Open questions / future work - **Phase 2 resume context inject** (above). -- **MCP tool `openviking_commit_pending`**: explicit commit for the model - to call, useful when user knows they're about to exit. +- **Native MCP commit-pending tool**: explicit commit for the model to call, + useful when user knows they're about to exit. - **Subagent hook events**: kimicode has them, codex doesn't yet. When codex adds them, we should hook to keep subagent memory threads separate from main session. diff --git a/examples/codex-memory-plugin/README.md b/examples/codex-memory-plugin/README.md index 232bd7eb41..83fd4534fa 100644 --- a/examples/codex-memory-plugin/README.md +++ b/examples/codex-memory-plugin/README.md @@ -5,12 +5,12 @@ Long-term semantic memory for [Codex](https://developers.openai.com/codex), powe This is the Codex counterpart to [`claude-code-memory-plugin`](../claude-code-memory-plugin). It hooks Codex's lifecycle to: - **Auto-recall** relevant memories on every `UserPromptSubmit` and inject them via `hookSpecificOutput.additionalContext` -- **Incremental capture on `Stop`** (turn end): append the new user/assistant turns to a single long-lived OpenViking session keyed by Codex `session_id`. No commit per turn. +- **Incremental capture on `Stop`** (turn end): append the new user turns to a single long-lived OpenViking session keyed by Codex `session_id`. Set `captureAssistantTurns=true` to include assistant transcript turns too. No commit per turn. - **Commit on `PreCompact`**: trigger OpenViking's memory extractor on the full pre-compact transcript before Codex summarizes it. - **Commit on `SessionStart` (source=startup|clear)**: active-window heuristic — if exactly one *other* state file was touched within the last 2 min, commit it (the just-ended session). On `≥2`, defer to idle-TTL sweep at the tail. `source=resume` is a hard no-op (short reconnects re-fire `resume` and we don't want to commit a still-active session). See `DESIGN.md` for the full decision tree. -- **MCP runtime bootstrap is lazy**: the MCP launcher (`start-memory-server.mjs`) installs runtime deps on first MCP invocation, not in a hook. +- **Native MCP registration**: the installer points Codex at the OpenViking server's built-in `/mcp` endpoint for explicit tools. -It also exposes explicit MCP tools (`openviking_recall`, `openviking_store`, `openviking_forget`, `openviking_health`) for manual use. +Explicit MCP tools are served by OpenViking itself, not by a bundled helper server. Codex receives the same native tool set documented in the [MCP Integration Guide](../../docs/en/guides/06-mcp-integration.md), such as `search`, `read`, `list`, `store`, `add_resource`, `grep`, `glob`, `forget`, and `health`. ## Quick Start @@ -22,7 +22,14 @@ Installation is first here, matching the shape of the [Claude Code integration d bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/codex-memory-plugin/setup-helper/install.sh) ``` -The installer checks `codex`, `git`, and Node.js 22+, clones OpenViking to `~/.openviking/openviking-repo` if needed, registers a local `openviking-plugins-local` marketplace, enables `openviking-memory@openviking-plugins-local`, sets `features.plugin_hooks = true`, and pre-populates Codex's plugin cache so the plugin resolves immediately. It uses `~/.openviking/ovcli.conf` when present; otherwise the plugin falls back to `http://127.0.0.1:1933`. +The installer checks `codex`, `git`, and Node.js 22+, clones OpenViking to `~/.openviking/openviking-repo` if needed, registers a local `openviking-plugins-local` marketplace, enables `openviking-memory@openviking-plugins-local`, sets `features.plugin_hooks = true`, optionally registers `mcp_servers.openviking` with Codex's native HTTP MCP transport, and pre-populates Codex's plugin cache so the plugin resolves immediately. It uses `~/.openviking/ovcli.conf` when present; otherwise the plugin falls back to `http://127.0.0.1:1933`. + +Native MCP tools are recommended but optional. In an interactive shell the installer asks whether to enable them; in non-interactive runs the default is enabled. Use `OPENVIKING_CODEX_ENABLE_MCP=0` for a hooks-only install; this removes the installer-managed `mcp_servers.openviking` entry if one exists: + +```bash +OPENVIKING_CODEX_ENABLE_MCP=0 \ + bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/codex-memory-plugin/setup-helper/install.sh) +``` If you'd rather do it by hand, use the manual setup below. @@ -72,6 +79,23 @@ enabled = true EOF ``` +#### 3. Optional: enable native MCP tools + +This registers OpenViking's server-side `/mcp` endpoint with Codex. Skip it if you want hooks-only behavior. + +```toml +[mcp_servers.openviking] +url = "https://ov.example.com/mcp" +bearer_token_env_var = "OPENVIKING_API_KEY" +startup_timeout_sec = 30 +tool_timeout_sec = 120 + +[mcp_servers.openviking.http_headers] +"X-OpenViking-Account" = "default" +"X-OpenViking-User" = "" +"X-OpenViking-Agent" = "codex" +``` + For local development, pre-populate Codex's plugin cache so it resolves immediately: ```bash @@ -80,7 +104,9 @@ mkdir -p "$INSTALL_DIR" cp -R /abs/path/to/OpenViking/examples/codex-memory-plugin "$INSTALL_DIR/0.4.0" ``` -#### 3. Configure OpenViking +Run `codex mcp get openviking` to inspect the native MCP registration when enabled. + +#### 4. Configure OpenViking Use the same client config file as the `ov` CLI: @@ -94,26 +120,27 @@ Use the same client config file as the `ov` CLI: } ``` -Local server mode works without this file; the plugin falls back to `http://127.0.0.1:1933`. - -#### 4. Start Codex +The hooks read `ovcli.conf` directly. Codex's native HTTP MCP transport does not read that file, so `~/.codex/config.toml` needs a literal `/mcp` URL and `bearer_token_env_var` as shown above. Start Codex with the matching bearer token in the environment: ```bash +export OPENVIKING_API_KEY= codex ``` -First MCP launch installs runtime deps; later launches reuse them. - -### Development from source +Local server mode works without this file; hooks and MCP both fall back to `http://127.0.0.1:1933` if you configure that URL. -Only needed when editing `src/memory-server.ts`: +#### 5. Start Codex ```bash -cd examples/codex-memory-plugin -npm install -npm run build +codex ``` +Inside Codex, run `/mcp` to confirm the `openviking` entry points at your OpenViking `/mcp` URL and authenticates successfully. + +### Development from source + +No build step is required. The plugin scripts are plain Node.js modules and the explicit MCP tools are served by the OpenViking server. + `codex exec` does not reliably fire plugin lifecycle hooks in current Codex builds. For hook validation, use an interactive `codex` session or the scripts in `hooks/hooks.json` with synthetic JSON input. ## Configuration @@ -127,6 +154,8 @@ Resolution priority, highest to lowest: Auth is sent as `Authorization: Bearer ` plus legacy `X-API-Key` during migration. +Native MCP auth is handled by Codex, not by the hook scripts. The installer writes `mcp_servers.openviking.bearer_token_env_var` to `~/.codex/config.toml`; keep that env var set when starting Codex. + Optional Codex-specific tuning can live under `codex` in `ovcli.conf`: ```jsonc @@ -142,6 +171,8 @@ Optional Codex-specific tuning can live under `codex` in `ovcli.conf`: } ``` +The native MCP server has its own Codex config entry. Codex's HTTP MCP transport does not read `ovcli.conf`, so the installer resolves that file once and writes a literal URL/header block to `~/.codex/config.toml`. If you change the OpenViking URL/account/user later, rerun the installer or update `mcp_servers.openviking` manually. + ## Architecture ``` @@ -167,16 +198,12 @@ Optional Codex-specific tuning can live under `codex` in `ovcli.conf`: │ /api/v1/content/read │ └───────────────────────────────────────────┘ - ┌──────────────────────────────────────┐ - │ MCP Server (memory-server.ts) │ - │ Tools for explicit use: │ - │ • openviking_recall │ - │ • openviking_store │ - │ • openviking_forget │ - │ • openviking_health │ - │ Lazily npm ci's its runtime on │ - │ first launch. │ - └──────────────────────────────────────┘ + Codex native MCP transport + │ + ▼ + OpenViking server /mcp + Tools for explicit use: search, read, list, store, add_resource, grep, + glob, forget, health (plus any server-version-specific aliases). ``` ## How It Works @@ -199,7 +226,7 @@ On `startup` or `clear`, the script: On any /commit failure (OV unreachable, non-2xx, timeout) we **preserve state** (don't `clearState`) so the next sweep can retry. A transient OV outage shouldn't lose memory. -MCP runtime install does **not** live in this hook — it lazily runs from `scripts/start-memory-server.mjs` on first MCP launch. +MCP registration does **not** live in this hook. The installer writes Codex's native HTTP MCP config to `~/.codex/config.toml`. ### Auto-recall (every UserPromptSubmit) @@ -234,11 +261,11 @@ Two fallbacks recover the orphan: 1. **Idle-TTL sweep**: the next `SessionStart` (source=startup|clear) on the same machine commits any state file older than 30 min (`OPENVIKING_CODEX_IDLE_TTL_MS`). So as long as you start another codex session within ~30 min, the orphan is reclaimed. 2. **Active-window heuristic**: if you run `/new` or `/clear` shortly after the orphaned session was last touched, the heuristic catches it as the unique "recently-active" state and commits it deterministically. -The remaining limitation: if you never start another codex on this machine, no sweep runs and the OV session stays open server-side. If you care about preserving memory from a particular session before exiting, run `/compact` first or call `openviking_store` with the conclusions you want kept. +The remaining limitation: if you never start another codex on this machine, no sweep runs and the OV session stays open server-side. If you care about preserving memory from a particular session before exiting, run `/compact` first or call the native MCP `store` tool with the conclusions you want kept. ### MCP tools (explicit, on demand) -The MCP server provides tools for when Codex or the user needs explicit memory operations. See "Tools" below. +The OpenViking server's native `/mcp` endpoint provides tools for when Codex or the user needs explicit memory operations. See "MCP Tools" below. ## Codex hook output schema @@ -313,7 +340,6 @@ If step 6 returns no leaf memories, check: | `captureMaxLength` | `24000` | Max text length for capture | | `captureTimeoutMs` | `30000` | HTTP request timeout for capture/commit (ms) | | `captureAssistantTurns` | `false` | Include assistant turns in transcript-incremental capture | -| `captureLastAssistantOnStop` | `true` | Capture `last_assistant_message` separately on every Stop | | `autoCommitOnCompact` | `true` | Commit the full transcript on `PreCompact` | | `debug` | `false` | Write structured debug logs | @@ -349,13 +375,15 @@ State-file / SessionStart tuning: Requests send both `Authorization: Bearer ` (primary — required by OpenViking Cloud) and `X-API-Key` (legacy — accepted by older self-hosted servers). The legacy header will be dropped once `X-API-Key` is fully retired upstream. +For native MCP, Codex sends `Authorization: Bearer $OPENVIKING_API_KEY` from `bearer_token_env_var` plus the static account/user/agent headers written under `[mcp_servers.openviking.http_headers]`. + ## Hook timeouts | Hook | Default timeout | Notes | |------|-----------------|-------| -| `SessionStart` | `120s` | First session may need time to install runtime deps | +| `SessionStart` | `30s` | Orphan commit / idle sweep | | `UserPromptSubmit` | `8s` | Recall must stay fast — keep `timeoutMs` low | -| `Stop` | `45s` | Gives capture room to finish | +| `Stop` | `30s` | Gives capture room to finish | | `PreCompact` | `60s` | Whole transcript posts plus commit | ## Debug logging @@ -364,37 +392,7 @@ Set `OPENVIKING_DEBUG=1` or `codex.debug=true` in `ovcli.conf` to write structur ## MCP Tools -### `openviking_recall` - -Search OpenViking memory. - -Parameters: - -- `query`: search query -- `target_uri`: optional search scope, default `viking://user/memories` -- `limit`: optional max results -- `score_threshold`: optional minimum score - -### `openviking_store` - -Store a memory by creating a short OpenViking session, adding the text, and committing. Memory creation is extraction-dependent; the tool reports when OpenViking commits the session but extracts zero items. - -Parameters: - -- `text`: information to store -- `role`: optional message role, default `user` - -### `openviking_forget` - -Delete an exact memory URI. Use `openviking_recall` first to find the URI. - -Parameters: - -- `uri`: exact `viking://user/.../memories/...` or `viking://agent/.../memories/...` - -### `openviking_health` - -Check server reachability. +Codex uses OpenViking's native `/mcp` endpoint. Current deployed servers expose `search`, `read`, `list`, `store`, `add_resource`, `grep`, `glob`, `forget`, and `health`; newer server builds may expose aliases such as `find` and `remember`. See the [MCP Integration Guide](../../docs/en/guides/06-mcp-integration.md) for schemas and examples. ## Plugin Structure @@ -407,19 +405,14 @@ codex-memory-plugin/ ├── scripts/ │ ├── config.mjs # Shared config loader (ovcli.conf + env) │ ├── debug-log.mjs # Structured JSONL logger -│ ├── runtime-common.mjs # Plugin data root + install-state helpers -│ ├── bootstrap-runtime.mjs # SessionStart installer -│ ├── start-memory-server.mjs # Launches MCP server through the runtime │ ├── auto-recall.mjs # UserPromptSubmit hook │ ├── auto-capture.mjs # Stop hook -│ └── pre-compact-capture.mjs # PreCompact hook (commits full transcript) -├── servers/ -│ └── memory-server.js # Compiled MCP server (checked in) -├── src/ -│ └── memory-server.ts # MCP server source -├── .mcp.json # MCP server definition (consumed by Codex) +│ ├── pre-compact-capture.mjs # PreCompact hook (commits full transcript) +│ ├── session-start-commit.mjs # SessionStart orphan commit / idle sweep +│ └── session-state.mjs # State-file persistence +├── setup-helper/ +│ └── install.sh # Installs hooks plugin + native /mcp config ├── package.json -├── tsconfig.json └── README.md ``` diff --git a/examples/codex-memory-plugin/VERIFICATION.md b/examples/codex-memory-plugin/VERIFICATION.md index 6ec3a4ff58..13c9566cf9 100644 --- a/examples/codex-memory-plugin/VERIFICATION.md +++ b/examples/codex-memory-plugin/VERIFICATION.md @@ -229,7 +229,31 @@ OPENVIKING_CONFIG_FILE=$OV_CONF ov read viking://user//memories/profi Expect new entries describing the captured preferences (favorite color, serif fonts, etc.) with timestamps from this run. -## 8. Codex CLI smoke test (requires codex auth) +## 8. Optional native MCP registration smoke + +The plugin no longer ships a private stdio MCP server. If native MCP is +enabled, Codex should point at OpenViking server `/mcp`. + +```bash +codex mcp get openviking --json +# Expect transport.type = "streamable_http", url = ".../mcp", +# and bearer_token_env_var = "OPENVIKING_API_KEY" (or your chosen env var). +``` + +Inside an interactive Codex session, run `/mcp` and call `health`. A live +OpenViking server should expose native tools such as `search`, `read`, `list`, +`store`/`remember`, `add_resource`, `grep`, `glob`, `forget`, and `health` +depending on the server version. + +Hooks-only installs are valid too: + +```bash +OPENVIKING_CODEX_ENABLE_MCP=0 \ + bash examples/codex-memory-plugin/setup-helper/install.sh +# Expect any installer-managed mcp_servers.openviking section to be removed. +``` + +## 9. Codex CLI smoke test (requires codex auth) ```bash codex plugin marketplace add /path/to/OpenViking-codex-marketplace # if not already diff --git a/examples/codex-memory-plugin/package-lock.json b/examples/codex-memory-plugin/package-lock.json deleted file mode 100644 index 409c4d2f3f..0000000000 --- a/examples/codex-memory-plugin/package-lock.json +++ /dev/null @@ -1,1174 +0,0 @@ -{ - "name": "codex-openviking-memory", - "version": "0.4.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "codex-openviking-memory", - "version": "0.4.0", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@types/node": { - "version": "22.19.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.18.tgz", - "integrity": "sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz", - "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hono": { - "version": "4.12.18", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", - "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/examples/codex-memory-plugin/package.json b/examples/codex-memory-plugin/package.json index 0798bda639..f2b2a08eab 100644 --- a/examples/codex-memory-plugin/package.json +++ b/examples/codex-memory-plugin/package.json @@ -1,19 +1,6 @@ { "name": "codex-openviking-memory", "version": "0.4.0", - "description": "OpenViking memory plugin for Codex — hooks (recall/capture/pre-compact) + MCP server for explicit memory operations.", - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "start": "node ./servers/memory-server.js" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" - } + "description": "OpenViking memory plugin for Codex — hooks for recall/capture/pre-compact plus native OpenViking /mcp registration.", + "type": "module" } diff --git a/examples/codex-memory-plugin/scripts/bootstrap-runtime.mjs b/examples/codex-memory-plugin/scripts/bootstrap-runtime.mjs deleted file mode 100644 index bc94705a08..0000000000 --- a/examples/codex-memory-plugin/scripts/bootstrap-runtime.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { - computeSourceState, - ensureRuntimeInstalled, - getRuntimePaths, -} from "./runtime-common.mjs"; - -async function main() { - // Codex hook stdin: JSON object — we ignore it (SessionStart payload). - // Read & discard to keep the pipe clean across platforms. - process.stdin.resume(); - for await (const _ of process.stdin) { /* drain */ } - - let paths; - try { - paths = getRuntimePaths(); - } catch (err) { - process.stderr.write( - `[openviking-memory] CODEX_PLUGIN_ROOT not set; skipping runtime bootstrap. ${err instanceof Error ? err.message : String(err)}\n`, - ); - return; - } - - const expectedState = await computeSourceState(paths); - - try { - await ensureRuntimeInstalled(paths, expectedState); - } catch (err) { - process.stderr.write( - `[openviking-memory] Failed to prepare MCP runtime dependencies: ${err instanceof Error ? err.message : String(err)}\n`, - ); - } -} - -main().catch((err) => { - process.stderr.write( - `[openviking-memory] Runtime bootstrap failed: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(0); -}); diff --git a/examples/codex-memory-plugin/scripts/config.mjs b/examples/codex-memory-plugin/scripts/config.mjs index 0d6c532e4b..e4070d2c07 100644 --- a/examples/codex-memory-plugin/scripts/config.mjs +++ b/examples/codex-memory-plugin/scripts/config.mjs @@ -7,10 +7,9 @@ * 3. ov.conf — the server config (server.* + optional codex.* block for tuning) * 4. Built-in defaults * - * Mirrors examples/claude-code-memory-plugin/scripts/config.mjs so the - * hook surface and the MCP server (src/memory-server.ts imports loadConfig - * from here) resolve identity identically. Aligning the two prevents - * silent identity drift between auto-capture and explicit `remember` calls. + * Mirrors examples/claude-code-memory-plugin/scripts/config.mjs where the + * hook surfaces overlap, while explicit MCP tools are provided by the + * OpenViking server's native /mcp endpoint. * * File-path env vars: * OPENVIKING_CLI_CONFIG_FILE alternate ovcli.conf path (preferred) @@ -228,7 +227,6 @@ export function loadConfig() { ))), captureTimeoutMs, captureAssistantTurns: envBool("OPENVIKING_CAPTURE_ASSISTANT_TURNS") ?? (cx.captureAssistantTurns === true), - captureLastAssistantOnStop: envBool("OPENVIKING_CAPTURE_LAST_ASSISTANT_ON_STOP") ?? (cx.captureLastAssistantOnStop !== false), autoCommitOnCompact: envBool("OPENVIKING_AUTO_COMMIT_ON_COMPACT") ?? (cx.autoCommitOnCompact !== false), diff --git a/examples/codex-memory-plugin/scripts/runtime-common.mjs b/examples/codex-memory-plugin/scripts/runtime-common.mjs deleted file mode 100644 index 5636c31b9b..0000000000 --- a/examples/codex-memory-plugin/scripts/runtime-common.mjs +++ /dev/null @@ -1,284 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { createHash } from "node:crypto"; -import { constants as fsConstants } from "node:fs"; -import { access, copyFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises"; -import { homedir } from "node:os"; -import { join } from "node:path"; - -export const INSTALL_TIMEOUT_MS = 120000; - -const LOCK_STALE_MS = 10 * 60 * 1000; -const FALLBACK_PLUGIN_DATA_ROOT = join(homedir(), ".openviking", "codex-memory-plugin"); -const RUNTIME_ENV_META_PATH = ".runtime-env.json"; - -export function getRuntimePaths() { - const pluginRoot = process.env.CODEX_PLUGIN_ROOT; - const pluginDataRoot = process.env.CODEX_PLUGIN_DATA || FALLBACK_PLUGIN_DATA_ROOT; - - if (!pluginRoot) throw new Error("CODEX_PLUGIN_ROOT is not set"); - - const runtimeRoot = join(pluginDataRoot, "runtime"); - - return { - pluginRoot, - pluginDataRoot, - runtimeRoot, - sourcePackagePath: join(pluginRoot, "package.json"), - sourceLockPath: join(pluginRoot, "package-lock.json"), - sourceConfigPath: join(pluginRoot, "scripts", "config.mjs"), - sourceServerPath: join(pluginRoot, "servers", "memory-server.js"), - runtimePackagePath: join(runtimeRoot, "package.json"), - runtimeLockPath: join(runtimeRoot, "package-lock.json"), - runtimeConfigPath: join(runtimeRoot, "scripts", "config.mjs"), - runtimeServerPath: join(runtimeRoot, "servers", "memory-server.js"), - runtimeNodeModulesPath: join(runtimeRoot, "node_modules"), - statePath: join(runtimeRoot, "install-state.json"), - lockDir: join(runtimeRoot, ".install-lock"), - envMetaPath: join(runtimeRoot, RUNTIME_ENV_META_PATH), - usingFallbackPluginData: !process.env.CODEX_PLUGIN_DATA, - }; -} - -export async function computeSourceState(paths) { - const [pkgRaw, lockRaw, configRaw, serverRaw] = await Promise.all([ - readFile(paths.sourcePackagePath), - readFile(paths.sourceLockPath), - readFile(paths.sourceConfigPath), - readFile(paths.sourceServerPath), - ]); - - const pkg = JSON.parse(pkgRaw.toString("utf8")); - - return { - pluginVersion: typeof pkg.version === "string" ? pkg.version : "0.0.0", - manifestHash: sha256(pkgRaw, lockRaw), - serverHash: sha256(configRaw, serverRaw), - }; -} - -export async function loadInstallState(paths) { - try { - return JSON.parse(await readFile(paths.statePath, "utf8")); - } catch { - return null; - } -} - -export async function writeInstallState(paths, state) { - await mkdir(paths.runtimeRoot, { recursive: true }); - await writeFile( - paths.statePath, - JSON.stringify( - { - ...state, - updatedAt: new Date().toISOString(), - }, - null, - 2, - ) + "\n", - ); -} - -export async function writeRuntimeEnvMeta(paths) { - await mkdir(paths.runtimeRoot, { recursive: true }); - await writeFile( - paths.envMetaPath, - JSON.stringify( - { - pluginDataRoot: paths.pluginDataRoot, - runtimeRoot: paths.runtimeRoot, - usingFallbackPluginData: paths.usingFallbackPluginData, - updatedAt: new Date().toISOString(), - }, - null, - 2, - ) + "\n", - ); -} - -export async function runtimeIsReady(paths, expectedState) { - const state = await loadInstallState(paths); - if (!state || state.status !== "ready") return false; - if (state.manifestHash !== expectedState.manifestHash) return false; - if (state.serverHash !== expectedState.serverHash) return false; - - for (const target of [ - paths.runtimePackagePath, - paths.runtimeLockPath, - paths.runtimeConfigPath, - paths.runtimeServerPath, - paths.runtimeNodeModulesPath, - ]) { - if (!(await pathExists(target))) return false; - } - - return true; -} - -export async function syncRuntimeFiles(paths) { - await mkdir(join(paths.runtimeRoot, "servers"), { recursive: true }); - await mkdir(join(paths.runtimeRoot, "scripts"), { recursive: true }); - await copyFile(paths.sourcePackagePath, paths.runtimePackagePath); - await copyFile(paths.sourceLockPath, paths.runtimeLockPath); - await copyFile(paths.sourceConfigPath, paths.runtimeConfigPath); - await copyFile(paths.sourceServerPath, paths.runtimeServerPath); - await writeRuntimeEnvMeta(paths); -} - -export async function acquireInstallLock(paths, timeoutMs = INSTALL_TIMEOUT_MS) { - const startedAt = Date.now(); - - while (Date.now() - startedAt < timeoutMs) { - await mkdir(paths.runtimeRoot, { recursive: true }); - - try { - await mkdir(paths.lockDir); - await writeFile( - join(paths.lockDir, "owner.json"), - JSON.stringify({ - pid: process.pid, - createdAt: new Date().toISOString(), - }) + "\n", - ); - return async () => { - await rm(paths.lockDir, { recursive: true, force: true }); - }; - } catch (err) { - if (err?.code !== "EEXIST") throw err; - - if (await isStaleLock(paths.lockDir)) { - await rm(paths.lockDir, { recursive: true, force: true }); - continue; - } - - await wait(500); - } - } - - throw new Error(`Timed out waiting for install lock in ${paths.runtimeRoot}`); -} - -export async function waitForRuntime(paths, expectedState, options = {}) { - const timeoutMs = options.timeoutMs ?? INSTALL_TIMEOUT_MS; - const pollMs = options.pollMs ?? 500; - const graceMs = options.graceMs ?? 5000; - const startedAt = Date.now(); - let sawLock = await pathExists(paths.lockDir); - - while (Date.now() - startedAt < timeoutMs) { - if (await runtimeIsReady(paths, expectedState)) return true; - - const lockExists = await pathExists(paths.lockDir); - sawLock ||= lockExists; - - if (!sawLock && Date.now() - startedAt >= graceMs) return false; - - await wait(pollMs); - } - - return runtimeIsReady(paths, expectedState); -} - -export function wait(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export async function ensureRuntimeInstalled(paths, expectedState) { - if (await runtimeIsReady(paths, expectedState)) return false; - - const releaseLock = await acquireInstallLock(paths, INSTALL_TIMEOUT_MS); - - try { - if (await runtimeIsReady(paths, expectedState)) return false; - - await syncRuntimeFiles(paths); - const result = spawnSync(getNpmCommand(), installArgs(), { - cwd: paths.runtimeRoot, - encoding: "utf8", - stdio: "pipe", - shell: process.platform === "win32", - }); - - if (result.error) throw result.error; - if (result.status !== 0) throw new Error(formatInstallFailure(result)); - - await writeInstallState(paths, { - status: "ready", - pluginVersion: expectedState.pluginVersion, - manifestHash: expectedState.manifestHash, - serverHash: expectedState.serverHash, - pluginDataRoot: paths.pluginDataRoot, - usingFallbackPluginData: paths.usingFallbackPluginData, - }); - - return true; - } catch (err) { - await rm(paths.runtimeNodeModulesPath, { recursive: true, force: true }); - - await writeInstallState(paths, { - status: "error", - pluginVersion: expectedState.pluginVersion, - manifestHash: expectedState.manifestHash, - serverHash: expectedState.serverHash, - pluginDataRoot: paths.pluginDataRoot, - usingFallbackPluginData: paths.usingFallbackPluginData, - error: err instanceof Error ? err.message : String(err), - }); - - throw err; - } finally { - await releaseLock(); - } -} - -async function pathExists(target) { - try { - await access(target, fsConstants.F_OK); - return true; - } catch { - return false; - } -} - -async function isStaleLock(lockDir) { - try { - const info = await stat(lockDir); - return Date.now() - info.mtimeMs > LOCK_STALE_MS; - } catch { - return false; - } -} - -function sha256(...buffers) { - const hash = createHash("sha256"); - for (const buf of buffers) hash.update(buf); - return hash.digest("hex"); -} - -function getNpmCommand() { - return process.platform === "win32" ? "npm.cmd" : "npm"; -} - -function installArgs() { - return ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"]; -} - -function formatInstallFailure(result) { - const lines = [ - `npm ci exited with status ${result.status ?? "unknown"}`, - trimOutput(result.stderr), - trimOutput(result.stdout), - ].filter(Boolean); - - return lines.join("\n"); -} - -function trimOutput(output) { - if (!output) return ""; - const text = output.trim(); - if (!text) return ""; - - const maxChars = 4000; - if (text.length <= maxChars) return text; - return text.slice(-maxChars); -} diff --git a/examples/codex-memory-plugin/scripts/start-memory-server.mjs b/examples/codex-memory-plugin/scripts/start-memory-server.mjs deleted file mode 100644 index 423b79420c..0000000000 --- a/examples/codex-memory-plugin/scripts/start-memory-server.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import { spawn } from "node:child_process"; -import { - computeSourceState, - ensureRuntimeInstalled, - getRuntimePaths, - loadInstallState, -} from "./runtime-common.mjs"; - -async function main() { - const paths = getRuntimePaths(); - const expectedState = await computeSourceState(paths); - - try { - await ensureRuntimeInstalled(paths, expectedState); - } catch { - const state = await loadInstallState(paths); - const detail = state?.error ? ` Last install error: ${state.error}` : ""; - process.stderr.write( - `[openviking-memory] MCP runtime is not ready in ${paths.runtimeRoot}.${detail}\n`, - ); - process.exit(1); - return; - } - - const child = spawn(process.execPath, [paths.runtimeServerPath], { - cwd: paths.runtimeRoot, - env: process.env, - stdio: "inherit", - }); - - for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) { - process.on(signal, () => { - if (!child.killed) child.kill(signal); - }); - } - - child.on("error", (err) => { - process.stderr.write( - `[openviking-memory] Failed to start MCP server: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(1); - }); - - child.on("exit", (code) => { - process.exit(code ?? 1); - }); -} - -main().catch((err) => { - process.stderr.write( - `[openviking-memory] MCP launcher failed: ${err instanceof Error ? err.message : String(err)}\n`, - ); - process.exit(1); -}); diff --git a/examples/codex-memory-plugin/servers/memory-server.js b/examples/codex-memory-plugin/servers/memory-server.js deleted file mode 100644 index 6dae165951..0000000000 --- a/examples/codex-memory-plugin/servers/memory-server.js +++ /dev/null @@ -1,274 +0,0 @@ -import { createHash } from "node:crypto"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; -// Shared with scripts/*.mjs so hook surface and MCP server resolve identity identically. -// Compiled output lives in servers/memory-server.js, so the relative path stays valid. -// @ts-ignore -- pure JS module, no type declarations -import { loadConfig } from "../scripts/config.mjs"; -function md5Short(value) { - return createHash("md5").update(value).digest("hex").slice(0, 12); -} -function clampScore(value) { - if (typeof value !== "number" || Number.isNaN(value)) - return 0; - return Math.max(0, Math.min(1, value)); -} -function isMemoryUri(uri) { - return /^viking:\/\/(?:user|agent)\/[^/]+\/memories(?:\/|$)/.test(uri); -} -function totalCommitMemories(result) { - return Object.values(result.memories_extracted ?? {}).reduce((sum, count) => sum + count, 0); -} -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} -const shared = loadConfig(); -const config = { - configPath: shared.configPath, - baseUrl: shared.baseUrl, - apiKey: shared.apiKey, - // The MCP server defaults missing account/user to "default" rather than "" so the - // X-OpenViking-* headers carry a value; hooks leave them empty (server-side default). - accountId: shared.account || "default", - userId: shared.user || "default", - agentId: shared.agentId || "codex", - timeoutMs: shared.timeoutMs, - recallLimit: shared.recallLimit, - scoreThreshold: shared.scoreThreshold, -}; -if (!config.baseUrl) { - process.stderr.write("[openviking-memory] No OpenViking URL resolved. Set OPENVIKING_URL or provide ovcli.conf / ov.conf.\n"); - process.exit(1); -} -class OpenVikingClient { - baseUrl; - apiKey; - accountId; - userId; - agentId; - timeoutMs; - runtimeIdentity = null; - constructor(baseUrl, apiKey, accountId, userId, agentId, timeoutMs) { - this.baseUrl = baseUrl; - this.apiKey = apiKey; - this.accountId = accountId; - this.userId = userId; - this.agentId = agentId; - this.timeoutMs = timeoutMs; - } - async request(path, init = {}) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), this.timeoutMs); - try { - const headers = new Headers(init.headers ?? {}); - if (this.apiKey) { - // OV Cloud only accepts Authorization: Bearer; self-hosted servers - // still accept X-API-Key, so we emit both for transition compat. - headers.set("Authorization", `Bearer ${this.apiKey}`); - headers.set("X-API-Key", this.apiKey); - } - if (this.accountId) - headers.set("X-OpenViking-Account", this.accountId); - if (this.userId) - headers.set("X-OpenViking-User", this.userId); - if (this.agentId) - headers.set("X-OpenViking-Agent", this.agentId); - if (init.body && !headers.has("Content-Type")) - headers.set("Content-Type", "application/json"); - const response = await fetch(`${this.baseUrl}${path}`, { - ...init, - headers, - signal: controller.signal, - }); - const payload = (await response.json().catch(() => ({}))); - if (!response.ok || payload.status === "error") { - const code = payload.error?.code ? ` [${payload.error.code}]` : ""; - const message = payload.error?.message ?? `HTTP ${response.status}`; - throw new Error(`OpenViking request failed${code}: ${message}`); - } - return (payload.result ?? payload); - } - finally { - clearTimeout(timer); - } - } - async healthCheck() { - try { - await this.request("/health"); - return true; - } - catch { - return false; - } - } - async getRuntimeIdentity() { - if (this.runtimeIdentity) - return this.runtimeIdentity; - const fallback = { userId: this.userId || "default", agentId: this.agentId || "default" }; - try { - const status = await this.request("/api/v1/system/status"); - const userId = typeof status.user === "string" && status.user.trim() ? status.user.trim() : fallback.userId; - this.runtimeIdentity = { userId, agentId: this.agentId || "default" }; - return this.runtimeIdentity; - } - catch { - this.runtimeIdentity = fallback; - return fallback; - } - } - async normalizeMemoryTargetUri(targetUri) { - const trimmed = targetUri.trim().replace(/\/+$/, ""); - const match = trimmed.match(/^viking:\/\/(user|agent)\/memories(?:\/(.*))?$/); - if (!match) - return trimmed; - const scope = match[1]; - const rest = match[2] ? `/${match[2]}` : ""; - const identity = await this.getRuntimeIdentity(); - const space = scope === "user" ? identity.userId : md5Short(`${identity.userId}:${identity.agentId}`); - return `viking://${scope}/${space}/memories${rest}`; - } - async find(query, targetUri, limit, scoreThreshold) { - const normalizedTargetUri = await this.normalizeMemoryTargetUri(targetUri); - return this.request("/api/v1/search/find", { - method: "POST", - body: JSON.stringify({ - query, - target_uri: normalizedTargetUri, - limit, - score_threshold: scoreThreshold, - }), - }); - } - async read(uri) { - return this.request(`/api/v1/content/read?uri=${encodeURIComponent(uri)}`); - } - async createSession() { - const result = await this.request("/api/v1/sessions", { - method: "POST", - body: JSON.stringify({}), - }); - return result.session_id; - } - async addSessionMessage(sessionId, role, content) { - await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`, { - method: "POST", - body: JSON.stringify({ role, content }), - }); - } - async commitSession(sessionId) { - const result = await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/commit`, { method: "POST", body: JSON.stringify({}) }); - if (!result.task_id) - return result; - const deadline = Date.now() + Math.max(this.timeoutMs, 30000); - while (Date.now() < deadline) { - await sleep(500); - const task = await this.getTask(result.task_id).catch(() => null); - if (!task) - break; - if (task.status === "completed") { - const taskResult = (task.result ?? {}); - return { - ...result, - status: "completed", - memories_extracted: (taskResult.memories_extracted ?? {}), - }; - } - if (task.status === "failed") - return { ...result, status: "failed", error: task.error }; - } - return { ...result, status: "timeout" }; - } - async getTask(taskId) { - return this.request(`/api/v1/tasks/${encodeURIComponent(taskId)}`, { method: "GET" }); - } - async deleteSession(sessionId) { - await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" }); - } - async deleteUri(uri) { - await this.request(`/api/v1/fs?uri=${encodeURIComponent(uri)}&recursive=false`, { method: "DELETE" }); - } -} -function formatMemoryResults(items) { - return items - .map((item, index) => { - const summary = item.abstract?.trim() || item.overview?.trim() || item.uri; - const score = Math.round(clampScore(item.score) * 100); - return `${index + 1}. ${summary}\n URI: ${item.uri}\n Score: ${score}%`; - }) - .join("\n\n"); -} -const client = new OpenVikingClient(config.baseUrl, config.apiKey, config.accountId, config.userId, config.agentId, config.timeoutMs); -const server = new McpServer({ name: "openviking-memory-codex", version: "0.1.0" }); -server.tool("openviking_recall", "Find OpenViking long-term memory.", { - query: z.string().describe("Find query"), - target_uri: z.string().optional().describe("Find scope URI, default viking://user/memories"), - limit: z.number().optional().describe("Max results, default 6"), - score_threshold: z.number().optional().describe("Minimum relevance score 0-1, default 0.01"), -}, async ({ query, target_uri, limit, score_threshold }) => { - const recallLimit = limit ?? config.recallLimit; - const threshold = score_threshold ?? config.scoreThreshold; - const result = await client.find(query, target_uri ?? "viking://user/memories", recallLimit, threshold); - const items = [...(result.memories ?? []), ...(result.resources ?? []), ...(result.skills ?? [])] - .filter((item) => clampScore(item.score) >= threshold) - .sort((left, right) => clampScore(right.score) - clampScore(left.score)) - .slice(0, recallLimit); - if (items.length === 0) { - return { content: [{ type: "text", text: "No relevant OpenViking memories found." }] }; - } - return { content: [{ type: "text", text: formatMemoryResults(items) }] }; -}); -server.tool("openviking_store", "Store information in OpenViking long-term memory.", { - text: z.string().describe("Information to store"), - role: z.string().optional().describe("Message role, default user"), -}, async ({ text, role }) => { - let sessionId; - try { - sessionId = await client.createSession(); - await client.addSessionMessage(sessionId, role || "user", text); - const result = await client.commitSession(sessionId); - const count = totalCommitMemories(result); - if (result.status === "failed") { - return { content: [{ type: "text", text: `Memory extraction failed: ${String(result.error)}` }] }; - } - if (result.status === "timeout") { - return { - content: [{ - type: "text", - text: `Memory extraction is still running (task_id=${result.task_id ?? "unknown"}).`, - }], - }; - } - if (count === 0) { - return { - content: [{ - type: "text", - text: "Committed session, but OpenViking extracted 0 memory item(s).", - }], - }; - } - return { content: [{ type: "text", text: `Stored memory. Extracted ${count} item(s).` }] }; - } - finally { - if (sessionId) - await client.deleteSession(sessionId).catch(() => { }); - } -}); -server.tool("openviking_forget", "Delete an exact OpenViking memory URI. Use openviking_recall first if you only have a query.", { - uri: z.string().describe("Exact memory URI to delete"), -}, async ({ uri }) => { - if (!isMemoryUri(uri)) { - return { content: [{ type: "text", text: `Refusing to delete non-memory URI: ${uri}` }] }; - } - await client.deleteUri(uri); - return { content: [{ type: "text", text: `Deleted memory: ${uri}` }] }; -}); -server.tool("openviking_health", "Check whether the OpenViking server is reachable.", {}, async () => { - const ok = await client.healthCheck(); - const text = ok - ? `OpenViking is reachable at ${config.baseUrl}.` - : `OpenViking is unreachable at ${config.baseUrl}.`; - return { content: [{ type: "text", text }] }; -}); -const transport = new StdioServerTransport(); -await server.connect(transport); diff --git a/examples/codex-memory-plugin/setup-helper/install.sh b/examples/codex-memory-plugin/setup-helper/install.sh index 69d31bf3b2..4b2f23d8fd 100755 --- a/examples/codex-memory-plugin/setup-helper/install.sh +++ b/examples/codex-memory-plugin/setup-helper/install.sh @@ -9,6 +9,29 @@ PLUGIN_NAME="openviking-memory" PLUGIN_ID="${PLUGIN_NAME}@${MARKETPLACE_NAME}" CODEX_CONFIG="${CODEX_CONFIG_FILE:-$HOME/.codex/config.toml}" +ENABLE_NATIVE_MCP="${OPENVIKING_CODEX_ENABLE_MCP:-}" +if [ -z "$ENABLE_NATIVE_MCP" ]; then + if [ -t 0 ]; then + printf 'Enable OpenViking native MCP tools in Codex? [Y/n] ' + read -r MCP_REPLY || MCP_REPLY="" + case "$MCP_REPLY" in + n|N|no|No|NO) ENABLE_NATIVE_MCP=0 ;; + *) ENABLE_NATIVE_MCP=1 ;; + esac + else + ENABLE_NATIVE_MCP=1 + fi +fi + +case "$ENABLE_NATIVE_MCP" in + 1|true|TRUE|yes|YES|y|Y) ENABLE_NATIVE_MCP=1 ;; + 0|false|FALSE|no|NO|n|N) ENABLE_NATIVE_MCP=0 ;; + *) + echo "Invalid OPENVIKING_CODEX_ENABLE_MCP=$ENABLE_NATIVE_MCP (expected 1/0 or true/false)." >&2 + exit 1 + ;; +esac + need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2 @@ -59,14 +82,17 @@ EOF codex plugin marketplace add "$MARKETPLACE_ROOT" >/dev/null 2>&1 || true -node - "$CODEX_CONFIG" "$PLUGIN_ID" <<'NODE' +node - "$CODEX_CONFIG" "$PLUGIN_ID" "$ENABLE_NATIVE_MCP" <<'NODE' const fs = require("node:fs"); -const path = process.argv[2]; +const os = require("node:os"); +const pathMod = require("node:path"); +const configPath = process.argv[2]; const pluginId = process.argv[3]; +const enableNativeMcp = process.argv[4] === "1"; let text = ""; try { - text = fs.readFileSync(path, "utf8"); + text = fs.readFileSync(configPath, "utf8"); } catch { text = ""; } @@ -127,11 +153,143 @@ function ensurePluginEnabled(src, pluginId) { return lines.join("\n").replace(/\n*$/, "\n"); } +function removeSection(src, section) { + let lines = src.split(/\n/); + const header = `[${section}]`; + + while (true) { + const start = lines.findIndex((line) => line.trim() === header); + if (start === -1) break; + + let end = lines.length; + for (let i = start + 1; i < lines.length; i += 1) { + if (/^\s*\[/.test(lines[i])) { + end = i; + break; + } + } + lines.splice(start, end - start); + } + + return lines.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "\n"); +} + +function removeNativeMcpServer(src) { + const withoutSections = removeSection( + removeSection(src, "mcp_servers.openviking.http_headers"), + "mcp_servers.openviking", + ); + return withoutSections + .replace(/^# OpenViking native MCP endpoint, managed by examples\/codex-memory-plugin\/setup-helper\/install\.sh\n/gm, "") + .replace(/\n{3,}/g, "\n\n") + .replace(/\n*$/, "\n"); +} + +function tomlString(value) { + return JSON.stringify(String(value)); +} + +function expandHome(filePath) { + if (!filePath) return filePath; + if (filePath === "~") return os.homedir(); + if (filePath.startsWith("~/")) return pathMod.join(os.homedir(), filePath.slice(2)); + return filePath; +} + +function readJson(filePath) { + if (!filePath) return null; + try { + return JSON.parse(fs.readFileSync(filePath, "utf8")); + } catch { + return null; + } +} + +function str(value, fallback = "") { + return typeof value === "string" && value.trim() ? value.trim() : fallback; +} + +function looksLikeOvcli(obj) { + if (!obj || typeof obj !== "object") return false; + if (obj.server && typeof obj.server === "object") return false; + return typeof obj.url === "string" || typeof obj.api_key === "string"; +} + +function loadOpenVikingConnection() { + const cliPath = expandHome(process.env.OPENVIKING_CLI_CONFIG_FILE) + || pathMod.join(os.homedir(), ".openviking", "ovcli.conf"); + const ovPath = expandHome(process.env.OPENVIKING_CONFIG_FILE) + || pathMod.join(os.homedir(), ".openviking", "ov.conf"); + + let cliFile = readJson(cliPath) || {}; + let ovFile = readJson(ovPath) || {}; + if (process.env.OPENVIKING_CONFIG_FILE && !process.env.OPENVIKING_CLI_CONFIG_FILE && looksLikeOvcli(ovFile)) { + cliFile = ovFile; + ovFile = {}; + } + + const server = ovFile.server || {}; + const codex = ovFile.codex || {}; + const host = str(server.host, "127.0.0.1").replace("0.0.0.0", "127.0.0.1"); + const port = Number.isFinite(Number(server.port)) ? Number(server.port) : 1933; + const baseUrl = str(process.env.OPENVIKING_URL, "") + || str(process.env.OPENVIKING_BASE_URL, "") + || str(cliFile.url, "") + || str(server.url, "") + || `http://${host}:${Math.trunc(port)}`; + + const bearerTokenEnvVar = process.env.OPENVIKING_BEARER_TOKEN && !process.env.OPENVIKING_API_KEY + ? "OPENVIKING_BEARER_TOKEN" + : "OPENVIKING_API_KEY"; + + return { + mcpUrl: `${baseUrl.replace(/\/+$/, "")}/mcp`, + bearerTokenEnvVar, + account: str(process.env.OPENVIKING_ACCOUNT, "") || str(cliFile.account, "") || str(codex.accountId, ""), + user: str(process.env.OPENVIKING_USER, "") || str(cliFile.user, "") || str(codex.userId, ""), + agentId: str(process.env.OPENVIKING_AGENT_ID, "") || str(cliFile.agent_id, "") || str(codex.agentId, "codex"), + }; +} + +function ensureNativeMcpServer(src) { + const conn = loadOpenVikingConnection(); + let next = removeNativeMcpServer(src); + + const lines = [ + "# OpenViking native MCP endpoint, managed by examples/codex-memory-plugin/setup-helper/install.sh", + "[mcp_servers.openviking]", + `url = ${tomlString(conn.mcpUrl)}`, + `bearer_token_env_var = ${tomlString(conn.bearerTokenEnvVar)}`, + "startup_timeout_sec = 30", + "tool_timeout_sec = 120", + ]; + + const headers = []; + if (conn.account) headers.push(["X-OpenViking-Account", conn.account]); + if (conn.user) headers.push(["X-OpenViking-User", conn.user]); + if (conn.agentId) headers.push(["X-OpenViking-Agent", conn.agentId]); + + if (headers.length > 0) { + lines.push("", "[mcp_servers.openviking.http_headers]"); + for (const [key, value] of headers) { + lines.push(`${tomlString(key)} = ${tomlString(value)}`); + } + } + + const prefix = next.trimEnd(); + return `${prefix}${prefix ? "\n\n" : ""}${lines.join("\n")}\n`; +} + text = ensurePluginEnabled(text, pluginId); text = ensureSectionLine(text, "features", "plugin_hooks", "true"); +if (enableNativeMcp) { + text = ensureNativeMcpServer(text); +} else { + text = removeNativeMcpServer(text); +} -fs.mkdirSync(require("node:path").dirname(path), { recursive: true }); -fs.writeFileSync(path, text); +fs.mkdirSync(pathMod.dirname(configPath), { recursive: true }); +fs.writeFileSync(configPath, text); NODE CACHE_DIR="$HOME/.codex/plugins/cache/$MARKETPLACE_NAME/$PLUGIN_NAME/$PLUGIN_VERSION" @@ -143,7 +301,7 @@ if [ ! -f "$HOME/.openviking/ovcli.conf" ]; then cat >&2 <<'EOF' Note: ~/.openviking/ovcli.conf was not found. -The plugin will use http://127.0.0.1:1933 unless OPENVIKING_URL / OPENVIKING_API_KEY are set. +Hooks and native MCP will default to http://127.0.0.1:1933 unless OPENVIKING_URL is set. EOF fi @@ -151,6 +309,24 @@ cat < - error?: unknown -} - -type TaskResult = { - status?: string - result?: Record - error?: unknown -} - -type SystemStatus = { - user?: unknown -} - -function md5Short(value: string): string { - return createHash("md5").update(value).digest("hex").slice(0, 12) -} - -function clampScore(value: number | undefined): number { - if (typeof value !== "number" || Number.isNaN(value)) return 0 - return Math.max(0, Math.min(1, value)) -} - -function isMemoryUri(uri: string): boolean { - return /^viking:\/\/(?:user|agent)\/[^/]+\/memories(?:\/|$)/.test(uri) -} - -function totalCommitMemories(result: CommitSessionResult): number { - return Object.values(result.memories_extracted ?? {}).reduce((sum, count) => sum + count, 0) -} - -function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -const shared = loadConfig() as { - baseUrl: string - apiKey: string - account: string - user: string - agentId: string - timeoutMs: number - recallLimit: number - scoreThreshold: number - configPath: string | null -} - -const config = { - configPath: shared.configPath, - baseUrl: shared.baseUrl, - apiKey: shared.apiKey, - // The MCP server defaults missing account/user to "default" rather than "" so the - // X-OpenViking-* headers carry a value; hooks leave them empty (server-side default). - accountId: shared.account || "default", - userId: shared.user || "default", - agentId: shared.agentId || "codex", - timeoutMs: shared.timeoutMs, - recallLimit: shared.recallLimit, - scoreThreshold: shared.scoreThreshold, -} - -if (!config.baseUrl) { - process.stderr.write( - "[openviking-memory] No OpenViking URL resolved. Set OPENVIKING_URL or provide ovcli.conf / ov.conf.\n", - ) - process.exit(1) -} - -class OpenVikingClient { - private runtimeIdentity: { userId: string; agentId: string } | null = null - - constructor( - private readonly baseUrl: string, - private readonly apiKey: string, - private readonly accountId: string, - private readonly userId: string, - private readonly agentId: string, - private readonly timeoutMs: number, - ) {} - - private async request(path: string, init: RequestInit = {}): Promise { - const controller = new AbortController() - const timer = setTimeout(() => controller.abort(), this.timeoutMs) - - try { - const headers = new Headers(init.headers ?? {}) - if (this.apiKey) { - // OV Cloud only accepts Authorization: Bearer; self-hosted servers - // still accept X-API-Key, so we emit both for transition compat. - headers.set("Authorization", `Bearer ${this.apiKey}`) - headers.set("X-API-Key", this.apiKey) - } - if (this.accountId) headers.set("X-OpenViking-Account", this.accountId) - if (this.userId) headers.set("X-OpenViking-User", this.userId) - if (this.agentId) headers.set("X-OpenViking-Agent", this.agentId) - if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json") - - const response = await fetch(`${this.baseUrl}${path}`, { - ...init, - headers, - signal: controller.signal, - }) - const payload = (await response.json().catch(() => ({}))) as { - status?: string - result?: T - error?: { code?: string; message?: string } - } - - if (!response.ok || payload.status === "error") { - const code = payload.error?.code ? ` [${payload.error.code}]` : "" - const message = payload.error?.message ?? `HTTP ${response.status}` - throw new Error(`OpenViking request failed${code}: ${message}`) - } - - return (payload.result ?? payload) as T - } finally { - clearTimeout(timer) - } - } - - async healthCheck(): Promise { - try { - await this.request("/health") - return true - } catch { - return false - } - } - - private async getRuntimeIdentity(): Promise<{ userId: string; agentId: string }> { - if (this.runtimeIdentity) return this.runtimeIdentity - - const fallback = { userId: this.userId || "default", agentId: this.agentId || "default" } - try { - const status = await this.request("/api/v1/system/status") - const userId = typeof status.user === "string" && status.user.trim() ? status.user.trim() : fallback.userId - this.runtimeIdentity = { userId, agentId: this.agentId || "default" } - return this.runtimeIdentity - } catch { - this.runtimeIdentity = fallback - return fallback - } - } - - async normalizeMemoryTargetUri(targetUri: string): Promise { - const trimmed = targetUri.trim().replace(/\/+$/, "") - const match = trimmed.match(/^viking:\/\/(user|agent)\/memories(?:\/(.*))?$/) - if (!match) return trimmed - - const scope = match[1] - const rest = match[2] ? `/${match[2]}` : "" - const identity = await this.getRuntimeIdentity() - const space = scope === "user" ? identity.userId : md5Short(`${identity.userId}:${identity.agentId}`) - return `viking://${scope}/${space}/memories${rest}` - } - - async find(query: string, targetUri: string, limit: number, scoreThreshold: number): Promise { - const normalizedTargetUri = await this.normalizeMemoryTargetUri(targetUri) - return this.request("/api/v1/search/find", { - method: "POST", - body: JSON.stringify({ - query, - target_uri: normalizedTargetUri, - limit, - score_threshold: scoreThreshold, - }), - }) - } - - async read(uri: string): Promise { - return this.request(`/api/v1/content/read?uri=${encodeURIComponent(uri)}`) - } - - async createSession(): Promise { - const result = await this.request<{ session_id: string }>("/api/v1/sessions", { - method: "POST", - body: JSON.stringify({}), - }) - return result.session_id - } - - async addSessionMessage(sessionId: string, role: string, content: string): Promise { - await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`, { - method: "POST", - body: JSON.stringify({ role, content }), - }) - } - - async commitSession(sessionId: string): Promise { - const result = await this.request( - `/api/v1/sessions/${encodeURIComponent(sessionId)}/commit`, - { method: "POST", body: JSON.stringify({}) }, - ) - - if (!result.task_id) return result - - const deadline = Date.now() + Math.max(this.timeoutMs, 30000) - while (Date.now() < deadline) { - await sleep(500) - const task = await this.getTask(result.task_id).catch(() => null) - if (!task) break - if (task.status === "completed") { - const taskResult = (task.result ?? {}) as Record - return { - ...result, - status: "completed", - memories_extracted: (taskResult.memories_extracted ?? {}) as Record, - } - } - if (task.status === "failed") return { ...result, status: "failed", error: task.error } - } - - return { ...result, status: "timeout" } - } - - async getTask(taskId: string): Promise { - return this.request(`/api/v1/tasks/${encodeURIComponent(taskId)}`, { method: "GET" }) - } - - async deleteSession(sessionId: string): Promise { - await this.request(`/api/v1/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" }) - } - - async deleteUri(uri: string): Promise { - await this.request(`/api/v1/fs?uri=${encodeURIComponent(uri)}&recursive=false`, { method: "DELETE" }) - } -} - -function formatMemoryResults(items: FindResultItem[]): string { - return items - .map((item, index) => { - const summary = item.abstract?.trim() || item.overview?.trim() || item.uri - const score = Math.round(clampScore(item.score) * 100) - return `${index + 1}. ${summary}\n URI: ${item.uri}\n Score: ${score}%` - }) - .join("\n\n") -} - -const client = new OpenVikingClient( - config.baseUrl, - config.apiKey, - config.accountId, - config.userId, - config.agentId, - config.timeoutMs, -) -const server = new McpServer({ name: "openviking-memory-codex", version: "0.1.0" }) - -server.tool( - "openviking_recall", - "Find OpenViking long-term memory.", - { - query: z.string().describe("Find query"), - target_uri: z.string().optional().describe("Find scope URI, default viking://user/memories"), - limit: z.number().optional().describe("Max results, default 6"), - score_threshold: z.number().optional().describe("Minimum relevance score 0-1, default 0.01"), - }, - async ({ query, target_uri, limit, score_threshold }) => { - const recallLimit = limit ?? config.recallLimit - const threshold = score_threshold ?? config.scoreThreshold - const result = await client.find(query, target_uri ?? "viking://user/memories", recallLimit, threshold) - const items = [...(result.memories ?? []), ...(result.resources ?? []), ...(result.skills ?? [])] - .filter((item) => clampScore(item.score) >= threshold) - .sort((left, right) => clampScore(right.score) - clampScore(left.score)) - .slice(0, recallLimit) - - if (items.length === 0) { - return { content: [{ type: "text" as const, text: "No relevant OpenViking memories found." }] } - } - - return { content: [{ type: "text" as const, text: formatMemoryResults(items) }] } - }, -) - -server.tool( - "openviking_store", - "Store information in OpenViking long-term memory.", - { - text: z.string().describe("Information to store"), - role: z.string().optional().describe("Message role, default user"), - }, - async ({ text, role }) => { - let sessionId: string | undefined - try { - sessionId = await client.createSession() - await client.addSessionMessage(sessionId, role || "user", text) - const result = await client.commitSession(sessionId) - const count = totalCommitMemories(result) - - if (result.status === "failed") { - return { content: [{ type: "text" as const, text: `Memory extraction failed: ${String(result.error)}` }] } - } - if (result.status === "timeout") { - return { - content: [{ - type: "text" as const, - text: `Memory extraction is still running (task_id=${result.task_id ?? "unknown"}).`, - }], - } - } - if (count === 0) { - return { - content: [{ - type: "text" as const, - text: "Committed session, but OpenViking extracted 0 memory item(s).", - }], - } - } - - return { content: [{ type: "text" as const, text: `Stored memory. Extracted ${count} item(s).` }] } - } finally { - if (sessionId) await client.deleteSession(sessionId).catch(() => {}) - } - }, -) - -server.tool( - "openviking_forget", - "Delete an exact OpenViking memory URI. Use openviking_recall first if you only have a query.", - { - uri: z.string().describe("Exact memory URI to delete"), - }, - async ({ uri }) => { - if (!isMemoryUri(uri)) { - return { content: [{ type: "text" as const, text: `Refusing to delete non-memory URI: ${uri}` }] } - } - - await client.deleteUri(uri) - return { content: [{ type: "text" as const, text: `Deleted memory: ${uri}` }] } - }, -) - -server.tool( - "openviking_health", - "Check whether the OpenViking server is reachable.", - {}, - async () => { - const ok = await client.healthCheck() - const text = ok - ? `OpenViking is reachable at ${config.baseUrl}.` - : `OpenViking is unreachable at ${config.baseUrl}.` - return { content: [{ type: "text" as const, text }] } - }, -) - -const transport = new StdioServerTransport() -await server.connect(transport) diff --git a/examples/codex-memory-plugin/tsconfig.json b/examples/codex-memory-plugin/tsconfig.json deleted file mode 100644 index 7f284c9a18..0000000000 --- a/examples/codex-memory-plugin/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "outDir": "./servers", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": false, - "sourceMap": false - }, - "include": ["src/**/*.ts"] -}