@openviking/opencode-plugin 0.2.4 → 2026.9.9-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL-ZH.md +22 -5
- package/INSTALL.md +23 -5
- package/README.md +46 -11
- package/index.mjs +9 -2
- package/lib/config.mjs +17 -3
- package/lib/mcp-config.mjs +2 -1
- package/lib/memory-recall.mjs +5 -2
- package/lib/memory-session.mjs +53 -9
- package/lib/shared/capture-utils.mjs +98 -19
- package/lib/shared/credentials.mjs +11 -0
- package/lib/shared/input-filters.mjs +219 -0
- package/lib/shared/mcp-proxy-config.mjs +124 -0
- package/lib/shared/mcp-proxy-core.mjs +13 -1
- package/lib/shared/pending-queue.mjs +11 -0
- package/lib/shared/recall-compress-core.mjs +17 -6
- package/lib/shared/recall-core.mjs +74 -5
- package/lib/shared/setup-wizard.mjs +14 -11
- package/lib/shared/uri-guard.mjs +28 -6
- package/lib/shared/workspace-identity.mjs +334 -0
- package/lib/shared/workspace-peer.mjs +126 -8
- package/lib/utils.mjs +7 -1
- package/package.json +3 -2
- package/servers/mcp-proxy.mjs +16 -40
- package/lib/shared/async-writer.mjs +0 -78
- package/lib/shared/plugin-config.mjs +0 -90
package/INSTALL-ZH.md
CHANGED
|
@@ -92,6 +92,7 @@ export { OpenVikingPlugin, default } from "./openviking/index.mjs"
|
|
|
92
92
|
```json
|
|
93
93
|
{
|
|
94
94
|
"enabled": true,
|
|
95
|
+
"mcp": { "enabled": true },
|
|
95
96
|
"timeoutMs": 30000,
|
|
96
97
|
"repoContext": { "enabled": true, "cacheTtlMs": 60000 },
|
|
97
98
|
"autoRecall": {
|
|
@@ -130,15 +131,29 @@ user/admin API key 的 API_KEY mode 时应留空。
|
|
|
130
131
|
|
|
131
132
|
高级场景可以用 `OPENVIKING_PLUGIN_CONFIG` 指向其他配置文件路径。
|
|
132
133
|
|
|
134
|
+
### 仅 Hooks 模式
|
|
135
|
+
|
|
136
|
+
如果其他 MCP server 已经提供 OpenViking,可以关闭本插件附带的 MCP 注册,同时保留生命周期 hooks:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"mcp": { "enabled": false }
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
repository context、自动 recall、消息 capture 和生命周期 commit 会继续工作,也不会添加或覆盖
|
|
145
|
+
OpenCode 的 `mcp.openviking` 配置。
|
|
146
|
+
|
|
133
147
|
## 验证
|
|
134
148
|
|
|
135
149
|
修改插件或 OpenViking 配置后,需要重启 OpenCode。
|
|
136
150
|
|
|
137
151
|
进入新的 OpenCode session 后,可以让 agent 浏览 OpenViking memory,或搜索一个已索引的资源。插件应暴露 OpenViking MCP server,OpenCode 中的工具名会带 `openviking_` 前缀:
|
|
138
152
|
|
|
139
|
-
- `
|
|
140
|
-
- `openviking_read`、`openviking_list`、`openviking_grep`、`openviking_glob`
|
|
141
|
-
- `openviking_remember`、`
|
|
153
|
+
- `openviking_search`、`openviking_find`
|
|
154
|
+
- `openviking_read`、`openviking_list`、`openviking_tree`、`openviking_grep`、`openviking_glob`
|
|
155
|
+
- `openviking_remember`、`openviking_write`、`openviking_edit`、`openviking_add_resource`
|
|
156
|
+
- `openviking_list_watches`、`openviking_cancel_watch`、`openviking_forget`、`openviking_health`
|
|
142
157
|
- `openviking_list_watches`、`openviking_cancel_watch`
|
|
143
158
|
|
|
144
159
|
如果行为异常,先查看运行时文件:
|
|
@@ -158,14 +173,16 @@ curl http://localhost:1933/health
|
|
|
158
173
|
|
|
159
174
|
插件会通过 OpenCode config 注册 OpenViking stdio MCP proxy。服务端实际返回的 `tools/list` 是最终工具清单;当前 OpenViking server 暴露:
|
|
160
175
|
|
|
161
|
-
- `
|
|
162
|
-
- `openviking_search`:跨 memories/resources/skills 的深度语义检索
|
|
176
|
+
- `openviking_search`:跨 memories/resources/skills 的深度语义检索;使用 `mode="context"` 获取面向当前任务、可直接注入的平衡上下文
|
|
163
177
|
- `openviking_find`:快速语义检索
|
|
164
178
|
- `openviking_remember`:存储重要事实或决策,供记忆提取
|
|
165
179
|
- `openviking_read`:读取一个或多个 `viking://` 文件
|
|
166
180
|
- `openviking_list`:列出 `viking://` 目录
|
|
181
|
+
- `openviking_tree`:展示 `viking://` 目录树
|
|
167
182
|
- `openviking_grep`:精确文本或正则搜索
|
|
168
183
|
- `openviking_glob`:glob 文件匹配
|
|
184
|
+
- `openviking_write`:创建、覆盖或追加 `viking://` 文件
|
|
185
|
+
- `openviking_edit`:对 `viking://` 文件做精确字符串替换
|
|
169
186
|
- `openviking_add_resource`:添加 URL、本地文件、sitemap 或 feed
|
|
170
187
|
- `openviking_forget`:在用户明确确认后删除 `viking://` URI
|
|
171
188
|
- `openviking_list_watches` / `openviking_cancel_watch`:查看或取消资源 watch
|
package/INSTALL.md
CHANGED
|
@@ -92,6 +92,7 @@ Example configuration:
|
|
|
92
92
|
```json
|
|
93
93
|
{
|
|
94
94
|
"enabled": true,
|
|
95
|
+
"mcp": { "enabled": true },
|
|
95
96
|
"timeoutMs": 30000,
|
|
96
97
|
"repoContext": { "enabled": true, "cacheTtlMs": 60000 },
|
|
97
98
|
"autoRecall": {
|
|
@@ -126,15 +127,30 @@ API keys are resolved from environment variables or `~/.openviking/ovcli.conf` a
|
|
|
126
127
|
|
|
127
128
|
For advanced setups, use `OPENVIKING_PLUGIN_CONFIG` to point to another configuration file path.
|
|
128
129
|
|
|
130
|
+
### Hook-only mode
|
|
131
|
+
|
|
132
|
+
If another MCP server already exposes OpenViking, set the bundled MCP registration to `false` while
|
|
133
|
+
keeping this plugin's lifecycle hooks active:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"mcp": { "enabled": false }
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Repository context, automatic recall, message capture, and lifecycle commits remain enabled. This
|
|
142
|
+
does not add or overwrite OpenCode's `mcp.openviking` entry.
|
|
143
|
+
|
|
129
144
|
## Verify
|
|
130
145
|
|
|
131
146
|
Restart OpenCode after changing plugin or OpenViking configuration.
|
|
132
147
|
|
|
133
148
|
In a new OpenCode session, ask the agent to browse OpenViking memory or search for a known indexed resource. The plugin should expose the OpenViking MCP server, with tools namespaced by OpenCode as `openviking_*`:
|
|
134
149
|
|
|
135
|
-
- `
|
|
136
|
-
- `openviking_read`, `openviking_list`, `openviking_grep`, `openviking_glob`
|
|
137
|
-
- `openviking_remember`, `
|
|
150
|
+
- `openviking_search`, `openviking_find`
|
|
151
|
+
- `openviking_read`, `openviking_list`, `openviking_tree`, `openviking_grep`, `openviking_glob`
|
|
152
|
+
- `openviking_remember`, `openviking_write`, `openviking_edit`, `openviking_add_resource`
|
|
153
|
+
- `openviking_list_watches`, `openviking_cancel_watch`, `openviking_forget`, `openviking_health`
|
|
138
154
|
- `openviking_list_watches`, `openviking_cancel_watch`
|
|
139
155
|
|
|
140
156
|
If anything looks wrong, check the runtime files:
|
|
@@ -154,14 +170,16 @@ curl http://localhost:1933/health
|
|
|
154
170
|
|
|
155
171
|
The plugin registers OpenViking's stdio MCP proxy through OpenCode config. The server's real `tools/list` response is the source of truth; current OpenViking servers expose:
|
|
156
172
|
|
|
157
|
-
- `
|
|
158
|
-
- `openviking_search`: deep semantic retrieval across memories, resources, and skills.
|
|
173
|
+
- `openviking_search`: deep semantic retrieval across memories, resources, and skills; use `mode="context"` for balanced, injection-ready context.
|
|
159
174
|
- `openviking_find`: fast semantic retrieval.
|
|
160
175
|
- `openviking_remember`: store important facts or decisions for memory extraction.
|
|
161
176
|
- `openviking_read`: read one or more `viking://` files.
|
|
162
177
|
- `openviking_list`: list a `viking://` directory.
|
|
178
|
+
- `openviking_tree`: show a `viking://` directory tree.
|
|
163
179
|
- `openviking_grep`: exact text or regex search.
|
|
164
180
|
- `openviking_glob`: glob file matching.
|
|
181
|
+
- `openviking_write`: create, overwrite, or append to a `viking://` file.
|
|
182
|
+
- `openviking_edit`: exact string replacement in a `viking://` file.
|
|
165
183
|
- `openviking_add_resource`: add a URL, local file, sitemap, or feed.
|
|
166
184
|
- `openviking_forget`: delete a `viking://` URI after explicit user confirmation.
|
|
167
185
|
- `openviking_list_watches` / `openviking_cancel_watch`: inspect or cancel resource watches.
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A unified OpenCode plugin for OpenViking repository retrieval and long-term memory.
|
|
4
4
|
|
|
5
|
+
> **Requires an OpenViking server with `viking://~` home-alias support.** Recall targets the
|
|
6
|
+
> caller's own context space through `viking://~/memories` and `viking://~/skills`; the uid-less
|
|
7
|
+
> `viking://user/memories` shorthand is rejected by newer servers.
|
|
8
|
+
|
|
5
9
|
This is the only OpenCode plugin example maintained in this repository. It supersedes the former split examples for indexed repository prompt injection and long-term memory.
|
|
6
10
|
|
|
7
11
|
The plugin uses OpenCode hooks for lifecycle behavior and registers OpenViking's standard stdio MCP proxy for model tools. It does not install or require an OpenCode skill, and agents do not need to run `ov` shell commands.
|
|
@@ -114,6 +118,7 @@ Create `~/.config/opencode/openviking-config.json`:
|
|
|
114
118
|
```json
|
|
115
119
|
{
|
|
116
120
|
"enabled": true,
|
|
121
|
+
"mcp": { "enabled": true },
|
|
117
122
|
"timeoutMs": 30000,
|
|
118
123
|
"repoContext": { "enabled": true, "cacheTtlMs": 60000 },
|
|
119
124
|
"autoRecall": {
|
|
@@ -140,14 +145,7 @@ when exact category ceilings are required.
|
|
|
140
145
|
API keys are resolved from environment variables or `~/.openviking/ovcli.conf` and sent as `Authorization: Bearer ...` by both hooks and the MCP proxy. Recall goes through the server-side context face (`POST /api/v1/search/search` with `mode="context"`), falling back to the deprecated `/api/v1/search/recall` on older deployments. `account` and `user` are trusted-mode identity
|
|
141
146
|
headers sent as `X-OpenViking-Account` and `X-OpenViking-User`; leave them empty
|
|
142
147
|
when using API-key mode with user/admin API keys.
|
|
143
|
-
By default the plugin derives a peer from the project directory
|
|
144
|
-
project-directory naming rule: every non-letter-or-digit character becomes `-`,
|
|
145
|
-
with no path normalization. For example, `/Users/x/Dev/OpenViking` becomes
|
|
146
|
-
`-Users-x-Dev-OpenViking`. Data-plane memory/resource requests send the
|
|
147
|
-
effective peer as `X-OpenViking-Actor-Peer`; captured session messages store it
|
|
148
|
-
as body `peer_id`. Configure `peerId` or `OPENVIKING_PEER_ID` to override the
|
|
149
|
-
workspace-derived peer, or set `workspacePeer=false` /
|
|
150
|
-
`OPENVIKING_WORKSPACE_PEER=0` to turn workspace-derived peers off.
|
|
148
|
+
By default the plugin derives a peer from the git identity of the project directory: the normalized `origin` URL, else the repository root path. Outside a git repository no peer is sent at all, and what is remembered there goes to the user-level space `viking://user/<you>/memories`. `git@github.com:volcengine/OpenViking.git` becomes `github.com-volcengine-openviking`; the path fallback keeps the older naming rule where every non-letter-or-digit character becomes `-`, so `/Users/x/Dev/OpenViking` becomes `-Users-x-Dev-OpenViking`. One repository therefore keeps one peer across subdirectories, worktrees, clones and machines, while a fork's different origin keeps it separate. Derivation reads `.git` directly, so no `git` binary is needed. The plugin does not read workspace `.openviking/config.json` files, so a `peer.id` written there has no effect. Data-plane memory/resource requests send the effective peer as `X-OpenViking-Actor-Peer`; captured session messages store it as body `peer_id`. Configure `peerId` or `OPENVIKING_PEER_ID` to override the derived peer, or set `workspacePeer=false` / `OPENVIKING_WORKSPACE_PEER=0` to send no peer at all. Memories written under the older directory-derived peer stay reachable: the default broad recall sweeps every peer under the user, and `recallPeerScope="actor"` asks that previous peer separately.
|
|
151
149
|
|
|
152
150
|
Recall defaults to the broad mode: global memory, the current workspace, and
|
|
153
151
|
other workspace memories can all be recalled, with other workspaces penalized
|
|
@@ -159,10 +157,28 @@ with an explicit actor peer so one person's memories are not recalled into
|
|
|
159
157
|
another person's session.
|
|
160
158
|
|
|
161
159
|
`OPENVIKING_API_KEY`, `OPENVIKING_ACCOUNT`, `OPENVIKING_USER`,
|
|
162
|
-
and `OPENVIKING_PEER_ID` take precedence over values in this file.
|
|
160
|
+
and `OPENVIKING_PEER_ID` take precedence over values in this file. The config
|
|
161
|
+
file's `peerId` still applies whenever shared credentials (ovcli.conf or
|
|
162
|
+
environment variables) do not carry a peer of their own, so an authenticated
|
|
163
|
+
setup keeps writing peer-scoped data instead of dropping into the shared user
|
|
164
|
+
tree.
|
|
163
165
|
|
|
164
166
|
For advanced setups, `OPENVIKING_PLUGIN_CONFIG` can point to another config file path.
|
|
165
167
|
|
|
168
|
+
### Hook-only mode
|
|
169
|
+
|
|
170
|
+
When OpenViking is already exposed through another MCP server, retain the lifecycle hooks while
|
|
171
|
+
skipping this plugin's bundled MCP registration:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"mcp": { "enabled": false }
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This leaves repository context, automatic recall, message capture, and lifecycle commits enabled.
|
|
180
|
+
It does not add or overwrite OpenCode's `mcp.openviking` entry.
|
|
181
|
+
|
|
166
182
|
OpenCode's local `read`, `glob`, and `grep` tools cannot read `viking://` URIs.
|
|
167
183
|
When the agent accidentally tries that, the plugin blocks the filesystem tool
|
|
168
184
|
call and points it to the OpenViking MCP tools.
|
|
@@ -171,14 +187,16 @@ call and points it to the OpenViking MCP tools.
|
|
|
171
187
|
|
|
172
188
|
OpenCode sees the OpenViking MCP server as `openviking`, so tool names are namespaced with `openviking_`.
|
|
173
189
|
|
|
174
|
-
- `
|
|
175
|
-
- `openviking_search`: deep semantic retrieval across memories, resources, and skills.
|
|
190
|
+
- `openviking_search`: deep semantic retrieval across memories, resources, and skills; use `mode="context"` for balanced, injection-ready context.
|
|
176
191
|
- `openviking_find`: fast semantic retrieval.
|
|
177
192
|
- `openviking_remember`: store important facts or decisions for memory extraction.
|
|
178
193
|
- `openviking_read`: read one or more `viking://` files.
|
|
179
194
|
- `openviking_list`: list a `viking://` directory.
|
|
195
|
+
- `openviking_tree`: show a `viking://` directory tree.
|
|
180
196
|
- `openviking_grep`: exact text or regex search.
|
|
181
197
|
- `openviking_glob`: glob file matching.
|
|
198
|
+
- `openviking_write`: create, overwrite, or append to a `viking://` file.
|
|
199
|
+
- `openviking_edit`: exact string replacement in a `viking://` file.
|
|
182
200
|
- `openviking_add_resource`: add a URL, local file, sitemap, or feed.
|
|
183
201
|
- `openviking_forget`: delete a `viking://` URI after explicit user confirmation.
|
|
184
202
|
- `openviking_list_watches` / `openviking_cancel_watch`: inspect or cancel resource watches.
|
|
@@ -194,3 +212,20 @@ The plugin writes runtime files to `~/.config/opencode/openviking/` by default:
|
|
|
194
212
|
- `openviking-session-state.json`
|
|
195
213
|
|
|
196
214
|
Set `runtime.dataDir` in config to override this directory.
|
|
215
|
+
|
|
216
|
+
## Automated npm releases
|
|
217
|
+
|
|
218
|
+
Changes merged into upstream `main` under `examples/opencode-plugin/` trigger
|
|
219
|
+
`.github/workflows/opencode-plugin-release.yml`. Releases use the same calendar
|
|
220
|
+
version convention as the OpenClaw plugin: `YYYY.M.D`, then `YYYY.M.D-N` for
|
|
221
|
+
subsequent releases on that date (Asia/Shanghai). No manual source version bump
|
|
222
|
+
is required. The workflow changes the version only in the package being published;
|
|
223
|
+
it does not commit generated version changes back to the repository.
|
|
224
|
+
|
|
225
|
+
Releases are serialized. A queued run checks out current `main`, so several quick
|
|
226
|
+
merges may be included in one package. The npm manifest records
|
|
227
|
+
`openvikingSourceCommit`; rerunning a successfully published commit skips publication.
|
|
228
|
+
Registry failures stop the workflow instead of treating an unavailable registry as
|
|
229
|
+
an unused version. Only upstream `main` can publish the official npm `latest` tag.
|
|
230
|
+
Manual dispatch on `main` retries a failed release using the existing npm credentials
|
|
231
|
+
or Trusted Publishing configuration.
|
package/index.mjs
CHANGED
|
@@ -44,8 +44,15 @@ export async function OpenVikingPlugin({ client, directory }) {
|
|
|
44
44
|
|
|
45
45
|
return {
|
|
46
46
|
config: async (opencodeConfig) => {
|
|
47
|
-
const injected = injectOpenVikingMcpConfig(opencodeConfig, pluginRoot)
|
|
48
|
-
|
|
47
|
+
const injected = injectOpenVikingMcpConfig(opencodeConfig, pluginRoot, config.mcp.enabled)
|
|
48
|
+
const hookOnly = !config.mcp.enabled
|
|
49
|
+
log(
|
|
50
|
+
injected || hookOnly ? "INFO" : "WARN",
|
|
51
|
+
"mcp",
|
|
52
|
+
injected ? "Registered OpenViking MCP server" :
|
|
53
|
+
hookOnly ? "Skipped bundled MCP registration in hook-only mode" :
|
|
54
|
+
"OpenViking MCP server was not registered",
|
|
55
|
+
)
|
|
49
56
|
},
|
|
50
57
|
|
|
51
58
|
event: async ({ event }) => {
|
package/lib/config.mjs
CHANGED
|
@@ -21,6 +21,9 @@ const DEFAULT_CONFIG = {
|
|
|
21
21
|
// it has to be switchable from the client that pays the latency.
|
|
22
22
|
recallQueryExpansion: "auto",
|
|
23
23
|
enabled: true,
|
|
24
|
+
mcp: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
},
|
|
24
27
|
timeoutMs: 30000,
|
|
25
28
|
runtime: {
|
|
26
29
|
dataDir: "",
|
|
@@ -42,7 +45,7 @@ const DEFAULT_CONFIG = {
|
|
|
42
45
|
captureMode: "semantic",
|
|
43
46
|
captureMaxLength: 24000,
|
|
44
47
|
captureAssistantTurns: true,
|
|
45
|
-
captureToolMaxChars:
|
|
48
|
+
captureToolMaxChars: 1000000,
|
|
46
49
|
commitTokenThreshold: 20000,
|
|
47
50
|
commitKeepRecentCount: 10,
|
|
48
51
|
profileTokenBudget: 10000,
|
|
@@ -120,6 +123,12 @@ function applyLegacyConnection(config, fileConfig) {
|
|
|
120
123
|
|
|
121
124
|
function applyBehaviorConfig(config, fileConfig = {}) {
|
|
122
125
|
if (fileConfig.enabled !== undefined) config.enabled = fileConfig.enabled !== false
|
|
126
|
+
const mcp = fileConfig.mcp && typeof fileConfig.mcp === "object" ? fileConfig.mcp : {}
|
|
127
|
+
config.mcp = {
|
|
128
|
+
...DEFAULT_CONFIG.mcp,
|
|
129
|
+
...mcp,
|
|
130
|
+
enabled: mcp.enabled !== false,
|
|
131
|
+
}
|
|
123
132
|
if (fileConfig.timeoutMs !== undefined) config.timeoutMs = fileConfig.timeoutMs
|
|
124
133
|
config.runtime = {
|
|
125
134
|
...DEFAULT_CONFIG.runtime,
|
|
@@ -236,6 +245,7 @@ function normalizeConfig(config) {
|
|
|
236
245
|
config.accountId = config.account
|
|
237
246
|
config.userId = config.user
|
|
238
247
|
config.userAgent = USER_AGENT
|
|
248
|
+
config.harness = "opencode"
|
|
239
249
|
config.timeoutMs = normalizeNumber(config.timeoutMs, DEFAULT_CONFIG.timeoutMs, 1000, 300000)
|
|
240
250
|
config.repoContext.cacheTtlMs = normalizeNumber(
|
|
241
251
|
config.repoContext.cacheTtlMs,
|
|
@@ -252,7 +262,7 @@ function normalizeConfig(config) {
|
|
|
252
262
|
config.recallPeerScope = config.recallPeerScope === "actor" ? "actor" : "all"
|
|
253
263
|
config.recallQueryExpansion = config.recallQueryExpansion === "off" ? "off" : "auto"
|
|
254
264
|
config.captureMaxLength = Math.max(200, Math.min(100000, Math.round(Number(config.captureMaxLength) || 24000)))
|
|
255
|
-
config.captureToolMaxChars = Math.max(200, Math.min(
|
|
265
|
+
config.captureToolMaxChars = Math.max(200, Math.min(1000000, Math.round(Number(config.captureToolMaxChars) || 1000000)))
|
|
256
266
|
config.commitTokenThreshold = Math.max(1000, Math.round(Number(config.commitTokenThreshold) || 20000))
|
|
257
267
|
const rawCommitKeepRecentCount = config.commitKeepRecentCount
|
|
258
268
|
const commitKeepRecentCount = rawCommitKeepRecentCount == null ||
|
|
@@ -285,7 +295,11 @@ export function loadConfig(pluginRoot, projectDirectory) {
|
|
|
285
295
|
config.apiKey = creds.apiKey
|
|
286
296
|
config.account = creds.account
|
|
287
297
|
config.user = creds.user
|
|
288
|
-
|
|
298
|
+
// Shared credentials only carry a peer when ovcli.conf sets actor_peer_id
|
|
299
|
+
// (or OPENVIKING_PEER_ID is exported); without one the project config's
|
|
300
|
+
// peerId still applies so authenticated setups keep writing peer-scoped
|
|
301
|
+
// data instead of silently dropping into the shared user tree (#4487).
|
|
302
|
+
config.peerId = creds.peerId || str(fileConfig.peerId, "")
|
|
289
303
|
config.mcpUrl = creds.mcpUrl
|
|
290
304
|
config.credentialSource = creds.credentialSource
|
|
291
305
|
config.credentialPath = creds.cliPath || creds.ovPath || ""
|
package/lib/mcp-config.mjs
CHANGED
|
@@ -11,8 +11,9 @@ export function createOpenVikingMcpConfig(pluginRoot) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function injectOpenVikingMcpConfig(config, pluginRoot) {
|
|
14
|
+
export function injectOpenVikingMcpConfig(config, pluginRoot, enabled = true) {
|
|
15
15
|
if (!config || typeof config !== "object") return false
|
|
16
|
+
if (!enabled) return false
|
|
16
17
|
config.mcp = config.mcp && typeof config.mcp === "object" ? config.mcp : {}
|
|
17
18
|
const current = config.mcp[OPENCODE_MCP_NAME]
|
|
18
19
|
if (current?.enabled === false) return false
|
package/lib/memory-recall.mjs
CHANGED
|
@@ -27,6 +27,7 @@ export function createMemoryRecall({ config, sessionManager }) {
|
|
|
27
27
|
query,
|
|
28
28
|
{
|
|
29
29
|
actorPeerId: effectivePeerId(config),
|
|
30
|
+
legacyPeerId: config.effectivePeer?.legacyPeerId ?? "",
|
|
30
31
|
// The mapped OV session is what turns on server-side query expansion
|
|
31
32
|
// and the cross-turn dedup ledger.
|
|
32
33
|
sessionId: sessionID ? sessionManager.getMappedSessionId(sessionID) : "",
|
|
@@ -57,8 +58,10 @@ export function extractCurrentUserText(parts) {
|
|
|
57
58
|
|
|
58
59
|
function prependSyntheticRecallPart(input, output, injection) {
|
|
59
60
|
const sessionID = input.sessionID ?? output.message?.sessionID
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
// messageID is optional in opencode's chat.message API (messageID?: string).
|
|
62
|
+
// When absent, generate a fallback so recall context is always injected.
|
|
63
|
+
const messageID = input.messageID ?? output.message?.id ?? `ov-recall-${Date.now()}`
|
|
64
|
+
if (!sessionID) return false
|
|
62
65
|
|
|
63
66
|
output.parts.unshift({
|
|
64
67
|
id: `prt-ov-recall-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
package/lib/memory-session.mjs
CHANGED
|
@@ -30,6 +30,20 @@ export function createMemorySessionManager({ config, pluginRoot }) {
|
|
|
30
30
|
const statePath = path.join(pluginRoot, "openviking-session-state.json")
|
|
31
31
|
const oldSessionMapPath = path.join(pluginRoot, "openviking-session-map.json")
|
|
32
32
|
let saveTimer = null
|
|
33
|
+
// Serialize saves: concurrent saveState() calls (a debounced save racing
|
|
34
|
+
// with flushAll / flushSession / session deletion) all share the same
|
|
35
|
+
// `${statePath}.tmp` temp file, so one rename can fail with ENOENT after
|
|
36
|
+
// another already moved it. Chaining through a promise queue keeps at most
|
|
37
|
+
// one write+rename in flight. Each queued save re-serializes the in-memory
|
|
38
|
+
// `sessions` map at execution time, so the last save always persists the
|
|
39
|
+
// latest state.
|
|
40
|
+
let saveChain = Promise.resolve()
|
|
41
|
+
|
|
42
|
+
function enqueueSave() {
|
|
43
|
+
const run = saveChain.then(() => saveState())
|
|
44
|
+
saveChain = run.catch(() => {})
|
|
45
|
+
return run
|
|
46
|
+
}
|
|
33
47
|
|
|
34
48
|
async function init() {
|
|
35
49
|
if (config.autoCapture) await migrateLegacySessionMap()
|
|
@@ -84,7 +98,7 @@ export function createMemorySessionManager({ config, pluginRoot }) {
|
|
|
84
98
|
function debouncedSaveState() {
|
|
85
99
|
if (saveTimer) clearTimeout(saveTimer)
|
|
86
100
|
saveTimer = setTimeout(() => {
|
|
87
|
-
|
|
101
|
+
enqueueSave().catch((error) => {
|
|
88
102
|
log("ERROR", "persistence", "Debounced save failed", { error: error?.message })
|
|
89
103
|
})
|
|
90
104
|
}, 300)
|
|
@@ -176,7 +190,7 @@ export function createMemorySessionManager({ config, pluginRoot }) {
|
|
|
176
190
|
if (!sessionId) return
|
|
177
191
|
await flushSession(sessionId, { commit: true, reason: event.type })
|
|
178
192
|
sessions.delete(sessionId)
|
|
179
|
-
await
|
|
193
|
+
await enqueueSave()
|
|
180
194
|
}
|
|
181
195
|
|
|
182
196
|
async function handleSessionError(event) {
|
|
@@ -250,10 +264,20 @@ export function createMemorySessionManager({ config, pluginRoot }) {
|
|
|
250
264
|
clearTimeout(saveTimer)
|
|
251
265
|
saveTimer = null
|
|
252
266
|
}
|
|
267
|
+
// Do not abort the whole dispose flush when one session fails (e.g. server
|
|
268
|
+
// already GC'd a stale session). Continue so later sessions still commit.
|
|
269
|
+
// See https://github.com/volcengine/OpenViking/issues/4490
|
|
253
270
|
for (const sessionId of sessions.keys()) {
|
|
254
|
-
|
|
271
|
+
try {
|
|
272
|
+
await flushSession(sessionId, { commit, reason: "flushAll" })
|
|
273
|
+
} catch (err) {
|
|
274
|
+
console.warn(
|
|
275
|
+
`[opencode-plugin] flushAll skipped session ${sessionId}:`,
|
|
276
|
+
err?.message || err,
|
|
277
|
+
)
|
|
278
|
+
}
|
|
255
279
|
}
|
|
256
|
-
await
|
|
280
|
+
await enqueueSave()
|
|
257
281
|
}
|
|
258
282
|
|
|
259
283
|
async function flushSession(opencodeSessionId, { commit = false, reason = "manual" } = {}) {
|
|
@@ -267,7 +291,7 @@ export function createMemorySessionManager({ config, pluginRoot }) {
|
|
|
267
291
|
} else if (added > 0) {
|
|
268
292
|
await maybeCommitByThreshold(state)
|
|
269
293
|
}
|
|
270
|
-
await
|
|
294
|
+
await enqueueSave()
|
|
271
295
|
return true
|
|
272
296
|
}
|
|
273
297
|
|
|
@@ -441,15 +465,35 @@ export function createMemorySessionManager({ config, pluginRoot }) {
|
|
|
441
465
|
for (const state of sessions.values()) {
|
|
442
466
|
if (state.ovSessionId === ovSessionId) state.lastCommitTime = Date.now()
|
|
443
467
|
}
|
|
444
|
-
|
|
445
|
-
|
|
468
|
+
const traceId = res.traceId || res.result?.trace_id
|
|
469
|
+
log("INFO", "session", "Committed OpenViking session", {
|
|
470
|
+
openviking_session: ovSessionId,
|
|
471
|
+
reason,
|
|
472
|
+
trace_id: traceId,
|
|
473
|
+
})
|
|
474
|
+
return { status: "accepted", result: res.result, traceId }
|
|
446
475
|
}
|
|
447
476
|
if (isRetryableFailure(res)) {
|
|
448
477
|
await enqueue("commitSession", ovSessionId, body)
|
|
449
|
-
log("WARN", "session", "Queued OpenViking session commit", {
|
|
478
|
+
log("WARN", "session", "Queued OpenViking session commit", {
|
|
479
|
+
openviking_session: ovSessionId,
|
|
480
|
+
reason,
|
|
481
|
+
trace_id: res.traceId,
|
|
482
|
+
status: res.status,
|
|
483
|
+
})
|
|
450
484
|
return { status: "queued" }
|
|
451
485
|
}
|
|
452
|
-
|
|
486
|
+
log("ERROR", "session", "Failed to commit OpenViking session", {
|
|
487
|
+
openviking_session: ovSessionId,
|
|
488
|
+
reason,
|
|
489
|
+
trace_id: res.traceId,
|
|
490
|
+
status: res.status,
|
|
491
|
+
error: res.error?.message || res.error?.code,
|
|
492
|
+
})
|
|
493
|
+
throw new Error(
|
|
494
|
+
`Failed to commit OpenViking session ${ovSessionId}: ${res.error?.message || res.status}` +
|
|
495
|
+
(res.traceId ? ` (trace_id=${res.traceId})` : ""),
|
|
496
|
+
)
|
|
453
497
|
}
|
|
454
498
|
|
|
455
499
|
async function migrateLegacySessionMap() {
|