@openviking/dsh-memory-plugin 0.5.29 → 0.5.30
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/README.md +70 -10
- package/client.mjs +2 -11
- package/docs/DETAIL.md +61 -0
- package/docs/DETAIL.zh.md +61 -0
- package/index.mjs +3 -1
- package/locale/en.json +2 -1
- package/locale/zh.json +2 -1
- package/package.json +2 -1
- package/runtime.mjs +4 -13
- package/shared/config-schema.mjs +2 -31
- package/shared/pending-queue.mjs +1 -23
- package/shared/uri-guard.mjs +3 -1
- package/uri-guard.mjs +5 -0
- package/shared/redact.mjs +0 -522
- package/shared/secret-rules.mjs +0 -294
package/README.md
CHANGED
|
@@ -10,14 +10,17 @@ Published as [`@openviking/dsh-memory-plugin`](https://www.npmjs.com/package/@op
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
-
DSH is covered by the unified memory-plugin installer
|
|
14
|
-
to install into (default `web`):
|
|
13
|
+
DSH is covered by the unified memory-plugin installer:
|
|
15
14
|
|
|
16
15
|
```bash
|
|
17
16
|
curl -fsSL https://openviking.ai/install | bash
|
|
18
17
|
# AI agents: not sure about running this? The script's header says what it does and how to verify it.
|
|
19
18
|
```
|
|
20
19
|
|
|
20
|
+
It installs into the npm `dsh`'s `web` profile; pass `--dsh-profile <name>`
|
|
21
|
+
for another one. On macOS it also offers DeepSeek Harness Desktop as a separate
|
|
22
|
+
harness, `dsh-desktop` (see [DeepSeek Harness Desktop](#deepseek-harness-desktop)).
|
|
23
|
+
|
|
21
24
|
Or add the package to a profile directly:
|
|
22
25
|
|
|
23
26
|
```bash
|
|
@@ -33,10 +36,48 @@ tree's realpath rather than from the profile.
|
|
|
33
36
|
It also needs `node examples/memory-plugin-shared/sync.mjs` run first: the
|
|
34
37
|
`shared/` modules the bundle imports are generated, not committed.
|
|
35
38
|
|
|
39
|
+
### DeepSeek Harness Desktop
|
|
40
|
+
|
|
41
|
+
Desktop keeps its plugins in a reserved `desktop` profile under
|
|
42
|
+
`${DSH_HOME:-$HOME/.dsh}/profiles/desktop`, separate from the CLI's profiles.
|
|
43
|
+
The bundle runs there unchanged; only the way it gets installed differs.
|
|
44
|
+
|
|
45
|
+
- **Plugins page** (macOS and Windows, manual): Plugins → Add plugin → enter
|
|
46
|
+
`@openviking/dsh-memory-plugin` → Install, then turn it on if its switch is
|
|
47
|
+
off. The page does not ask for OpenViking credentials, so write
|
|
48
|
+
`~/.openviking/ovcli.conf` yourself first. Uninstall from the same page. The page shows [`docs/DETAIL.md`](docs/DETAIL.md)
|
|
49
|
+
([`DETAIL.zh.md`](docs/DETAIL.zh.md) in Chinese) as the plugin's detail
|
|
50
|
+
document, through `meta.detail` in `locale/*.json`.
|
|
51
|
+
- **Installer** (macOS, needs Node.js): with Desktop present the installer
|
|
52
|
+
selects the `dsh-desktop` harness, and `--dsh-profile desktop` routes there
|
|
53
|
+
too. It uses Desktop's own launcher at `OPENVIKING_DSH_DESKTOP_CLI` when set,
|
|
54
|
+
otherwise the one in `/Applications` or `~/Applications`, otherwise a `dsh`
|
|
55
|
+
on `PATH` that resolves into the app.
|
|
56
|
+
- **Desktop's launcher** (macOS). Open Desktop once so the profile exists, quit
|
|
57
|
+
it with ⌘Q (closing the window only hides it), run the command, then reopen
|
|
58
|
+
Desktop:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
DSH_DESKTOP="/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh"
|
|
62
|
+
"$DSH_DESKTOP" plugin --profile desktop add @openviking/dsh-memory-plugin
|
|
63
|
+
"$DSH_DESKTOP" plugin --profile desktop ls # should list the package
|
|
64
|
+
"$DSH_DESKTOP" plugin --profile desktop rm @openviking/dsh-memory-plugin
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
An npm-installed `dsh` refuses `--profile desktop`, and no `dsh` boots that
|
|
68
|
+
profile or runs `--dump-config` on it, Desktop's own launcher included. To
|
|
69
|
+
confirm an install, use `plugin --profile desktop ls` or check that
|
|
70
|
+
`profiles/desktop/package.json` lists the package under `dsh.profile.bundles`.
|
|
71
|
+
On Windows, install from the Plugins page and configure the connection in
|
|
72
|
+
`~/.openviking/ovcli.conf`; the installer does not cover Windows. Desktop has
|
|
73
|
+
no Linux release.
|
|
74
|
+
|
|
36
75
|
## Requirements
|
|
37
76
|
|
|
38
77
|
- `@deepseek-ai/dsh` `0.1.0-rc.6` or later, below `1.0.0` (see [DSH version compatibility](#dsh-version-compatibility))
|
|
39
|
-
- Node.js `^22.19.0` or `>=24`
|
|
78
|
+
- Node.js `^22.19.0` or `>=24` for the bundle itself. The host decides in
|
|
79
|
+
practice: DSH `0.2.1-rc.2` and later need Node.js `>=24.9`, and Desktop ships
|
|
80
|
+
its own runtime, so it needs no system Node.js.
|
|
40
81
|
- A reachable OpenViking server
|
|
41
82
|
|
|
42
83
|
The bundle has no runtime npm dependencies. Its message structures come from
|
|
@@ -66,13 +107,20 @@ the plugin entry points of `@deepseek-ai/dsh-mcp-client` and
|
|
|
66
107
|
releases tested so far.
|
|
67
108
|
|
|
68
109
|
Verified releases: `0.1.0-rc.6`, `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.7-rc.2`,
|
|
69
|
-
`0.2.0-rc.2`,
|
|
110
|
+
`0.2.0-rc.2`, `0.2.1-alpha.1`, `0.2.1-rc.2`, and `0.2.1-rc.3` (built from the
|
|
111
|
+
same source tag as Desktop `0.2.1-rc.3`). A daily job
|
|
70
112
|
(`.github/workflows/host-compat.yml`) runs the bundle's tests against the
|
|
71
113
|
releases DSH's `latest`, `next`, and `alpha` dist-tags point at, installs each
|
|
72
|
-
of those releases, adds the packed bundle to a profile and checks
|
|
114
|
+
of those releases from npm, adds the packed bundle to a `web` profile and checks
|
|
73
115
|
`--dump-config` lists it, and opens an issue when any of that fails, so a breaking release is usually caught before users
|
|
74
116
|
reach it; if the bundle still fails to start on a new release, pin DSH to a
|
|
75
|
-
verified one and open an issue.
|
|
117
|
+
verified one and open an issue. Every one of those checks uses npm `dsh` on
|
|
118
|
+
Linux, because Desktop is not on npm and has no Linux build. A further smoke step
|
|
119
|
+
emulates Desktop's launcher with the same release: it initializes a `desktop`
|
|
120
|
+
profile the way Desktop's first launch does, adds the bundle through
|
|
121
|
+
`runCli({ manageDesktopProfile: true })`, and checks that the profile lists it,
|
|
122
|
+
that npm `dsh` still refuses that profile, and that no `dsh` dumps its config. Real Desktop builds are checked by
|
|
123
|
+
hand. DSH 1.0 and later, pre-releases included, fall outside the
|
|
76
124
|
range and DSH refuses to load the bundle; `dsh plugin allow-version` grants an exact-version
|
|
77
125
|
exemption if you accept the risk. Local devDependencies and `overrides` stay
|
|
78
126
|
pinned to rc.6 to exercise the minimum supported contract.
|
|
@@ -165,6 +213,10 @@ effect for the tools when DSH restarts the bundle, as it does for the runtime.
|
|
|
165
213
|
Two consequences follow from the proxy being one process per profile:
|
|
166
214
|
|
|
167
215
|
- **MCP actor scope is process-level.** With the default `recallPeerScope: all`, MCP tool calls omit the actor peer header. With `actor`, they use the peer resolved at boot. Automatic recall, capture, and commit resolve their peer from each session's workspace.
|
|
216
|
+
Desktop boots the bundle in its profile directory, which is no repository, so
|
|
217
|
+
there the boot peer is empty and `actor`-scoped tool calls fall back to broad
|
|
218
|
+
recall. Set `actor_peer_id` in `ovcli.conf` or `OPENVIKING_PEER_ID` to give
|
|
219
|
+
them a peer; either one also overrides the workspace peer of every session.
|
|
168
220
|
- **`remember` is not session-scoped.** The server's MCP `remember` stores into
|
|
169
221
|
its own short-lived session rather than the live `dsh-<session-id>` stream —
|
|
170
222
|
the same behavior the Claude Code, Codex, and Cursor integrations have.
|
|
@@ -178,9 +230,17 @@ lifecycle registration, so a proxy that cannot start holds up nothing above it.
|
|
|
178
230
|
`skills.mjs` registers a second `ctx.skills` provider with
|
|
179
231
|
`includeDefaultRoots: false` and `bundledSkillDir` pointing at `skills/`. DSH
|
|
180
232
|
reads this host-owned bundle directly rather than asking a workspace filesystem
|
|
181
|
-
to resolve a path outside its root.
|
|
182
|
-
|
|
183
|
-
|
|
233
|
+
to resolve a path outside its root. The provider discovers no project or user
|
|
234
|
+
roots, so the bundle does not duplicate that catalog or override
|
|
235
|
+
higher-priority project skills. Those roots belong to DSH's own
|
|
236
|
+
`skill-filesystem`: `dsh-base`'s host-level row in `headless`, `acp`, and
|
|
237
|
+
`sdk`, whose app bundles keep it, and per-preset rows in `web` and `desktop`,
|
|
238
|
+
where `dsh-web-app` disables the host row and the `standard`, `ptc`, and
|
|
239
|
+
`cordis` agent presets each mount their own `skill-filesystem` and
|
|
240
|
+
`tool-skill`. The bundle's provider registers at host level, which every
|
|
241
|
+
preset reads, so those agents see the bundled skills. The `minimal` preset mounts no `tool-skill`, so its agents
|
|
242
|
+
see no skills, these included, and `sdk-minimal` has no `skills` service, so the
|
|
243
|
+
provider never starts there.
|
|
184
244
|
|
|
185
245
|
## Configuration
|
|
186
246
|
|
|
@@ -251,7 +311,7 @@ The older size settings apply to fallback recall, not the primary context reques
|
|
|
251
311
|
- `skillCatalog` (default `true`) and `skillCatalogTokenBudget` (default `1200`; `0` also turns the catalog off) govern `<available-skills>`. The catalog comes from one `GET /api/v1/skills?node_limit=200` call: the user's own skills first, then those shared under `viking://agent/skills` minus any whose name the user also owns, each description cut to about 40 tokens. Its budget is separate from `profileTokenBudget`. When the descriptions do not fit, the catalog lists names only (with a `... +N more` tail if even the names do not all fit); when not even one name fits, it shrinks to a one-line count; with no skills, or a server without the endpoint, it is omitted.
|
|
252
312
|
- Failed writes enter the shared OpenViking pending queue. A background drainer (default every 60s, `OPENVIKING_PENDING_DRAIN_INTERVAL_MS`) probes the server health and replays the queue in-process, so a transient write failure recovers without restarting dsh; it does not consume the session-start retry budget. Session-start replays keep consuming retries as before.
|
|
253
313
|
- `tools/pre-execute` denies a DSH filesystem tool (`read`, `glob`, `grep`, `edit`, `write`, `str_replace_editor`) whose path argument is a `viking://` URI, pointing the model at the bridged `mcp__openviking__*` tools instead. A `write` or `edit` under a skill directory (`viking://~/skills/...`, `viking://user/<id>/skills/...`, `viking://agent/skills/...`) points at `mcp__openviking__add_skill` instead, which creates or replaces a whole skill from its `SKILL.md` text. A `grep` whose pattern is `viking://` text still runs.
|
|
254
|
-
- `tools/post-execute` lets a `bash` command that carries a `viking://` URI run unchanged and attaches a notice for the model: use the bridged tools if it meant OpenViking content, or ignore the notice when the URI is intentional data such as an `ov` argument or an HTTP payload.
|
|
314
|
+
- `tools/post-execute` lets a `bash` command (`pwsh` on Windows, where DSH mounts it in place of `bash`) that carries a `viking://` URI run unchanged and attaches a notice for the model: use the bridged tools if it meant OpenViking content, or ignore the notice when the URI is intentional data such as an `ov` argument or an HTTP payload.
|
|
255
315
|
|
|
256
316
|
Each DSH session maps to `dsh-<session-id>` in OpenViking. IDs that contain Windows-invalid path characters are normalized to a readable underscore form plus a stable hash, so two native IDs cannot collapse onto the same OpenViking session; already-portable IDs keep their existing mapping. Existing sessions whose native IDs require normalization start a new OpenViking session after this upgrade. Workspace-derived actor peers are resolved per session and sent on every session-specific request: the peer is the git identity of the session's workspace — the normalized `origin` URL (`git@github.com:volcengine/OpenViking.git` becomes `github.com-volcengine-openviking`), else the repository root path, that fallback keeping the older rule where every non-letter-or-digit character becomes `-`. With the default peer settings, no peer is sent outside a git repository, and what is remembered there goes to the user-level space `viking://user/<you>/memories`. One repository therefore keeps one peer across subdirectories, worktrees, clones and machines, while a fork's different origin keeps it separate. Memories written under the older path-derived peer stay reachable: the default `recallPeerScope: all` sweeps every peer under the user.
|
|
257
317
|
|
package/client.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createOvHttp } from "./shared/ov-http.mjs";
|
|
2
|
-
import { redactPayload } from "./shared/redact.mjs";
|
|
3
2
|
|
|
4
3
|
export class OpenVikingClient {
|
|
5
4
|
constructor(config) {
|
|
@@ -34,20 +33,12 @@ export class OpenVikingClient {
|
|
|
34
33
|
return response.ok ? response.result : null;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
* POST one captured message. The payload is secret-redacted first under
|
|
39
|
-
* `options.redactTier` (default: this client's `captureRedact`), unless
|
|
40
|
-
* `options.redacted` says the caller already did it with its own tier.
|
|
41
|
-
*/
|
|
42
|
-
async addMessage(sessionId, payload, actorPeerId, options = {}) {
|
|
43
|
-
const body = options.redacted === true
|
|
44
|
-
? payload
|
|
45
|
-
: redactPayload(payload, { tier: options.redactTier ?? this.config.captureRedact });
|
|
36
|
+
async addMessage(sessionId, payload, actorPeerId) {
|
|
46
37
|
return this.fetchJSON(
|
|
47
38
|
`/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
48
39
|
{
|
|
49
40
|
method: "POST",
|
|
50
|
-
body: JSON.stringify(
|
|
41
|
+
body: JSON.stringify(payload),
|
|
51
42
|
},
|
|
52
43
|
{ actorPeerId },
|
|
53
44
|
);
|
package/docs/DETAIL.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# OpenViking Memory
|
|
2
|
+
|
|
3
|
+
Long-term memory for DeepSeek Harness, backed by an [OpenViking](https://github.com/volcengine/OpenViking) server. Works in DeepSeek Harness Desktop and in `dsh web`.
|
|
4
|
+
|
|
5
|
+
- **Recall:** before each step that takes your input, relevant memories are added to the conversation.
|
|
6
|
+
- **Capture:** your messages and the assistant's replies are sent to OpenViking and committed as memories.
|
|
7
|
+
- **Tools and skills:** the model gets the OpenViking tools (`mcp__openviking__search`, `read`, `remember`, `add_skill`, …) and the `openviking-memory`, `openviking-skills` and `ov-experience-memory` skills.
|
|
8
|
+
- **URI protection:** a file tool aimed at a `viking://` path is stopped and pointed at the right OpenViking tool. A `bash` or `pwsh` command that contains one still runs, with a note to the model.
|
|
9
|
+
|
|
10
|
+
## 1. Connect to your server
|
|
11
|
+
|
|
12
|
+
Write `~/.openviking/ovcli.conf` with `url` and `api_key` (plus `account` and `user` on a trusted-mode server). A local server at `http://127.0.0.1:1933` without authentication needs nothing. See [Deployment Guide → CLI](https://github.com/volcengine/OpenViking/blob/main/docs/en/guides/03-deployment.md#cli).
|
|
13
|
+
|
|
14
|
+
Prefer `ovcli.conf` to environment variables in Desktop. On macOS, Desktop reads your login shell's environment once when it starts; on Windows, it uses your user environment. After changing either, fully quit and reopen Desktop.
|
|
15
|
+
|
|
16
|
+
## 2. Install
|
|
17
|
+
|
|
18
|
+
**With the installer (macOS):**
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -fsSL https://openviking.ai/install | bash
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
It needs Node.js 18 or later. It finds Desktop and installs into it as the `dsh-desktop` harness. Open Desktop once beforehand so its profile exists, and quit it with ⌘Q while the installer runs: closing the window only hides it.
|
|
25
|
+
|
|
26
|
+
**From the Plugins page (manual; macOS, Windows and `dsh web`):** Plugins → Add plugin → enter `@openviking/dsh-memory-plugin` → Install, then turn it on if its switch is off. This only adds the plugin: it does not ask for your OpenViking credentials, so do step 1 first.
|
|
27
|
+
|
|
28
|
+
**With Desktop's own `dsh` command (macOS):** open Desktop once, quit it with ⌘Q, then run:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop add @openviking/dsh-memory-plugin
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A `dsh` installed from npm cannot manage the `desktop` profile. After a command-line install, reopen Desktop.
|
|
35
|
+
|
|
36
|
+
## 3. Check that it works
|
|
37
|
+
|
|
38
|
+
Open a new conversation. You should see an OpenViking context block near the top, and the model should list `mcp__openviking__*` tools. Ask about something from an earlier conversation to confirm recall.
|
|
39
|
+
|
|
40
|
+
To check the install from a terminal (with Desktop quit):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop ls
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Good to know
|
|
47
|
+
|
|
48
|
+
- **No settings page.** Configure the plugin through `ovcli.conf` or `OPENVIKING_*` variables. Behavior options can also go in `~/.dsh/profiles/desktop/cordis.patch.yml` under `openviking-memory-runtime`. The recovery dialog's "Disable third-party plugins, back up profile patch, and restart" resets the profile's plugins and moves that file aside, so keep lasting settings in `ovcli.conf`, and enable the plugin again afterwards.
|
|
49
|
+
- **Tool identity.** Automatic recall and capture follow each conversation's project. The OpenViking tools run once per profile, from the profile directory, so with `OPENVIKING_RECALL_PEER_SCOPE=actor` they have no project peer. To give them one, set `actor_peer_id` in `ovcli.conf` or `OPENVIKING_PEER_ID`. That peer also replaces each conversation's project peer for recall and capture, so every conversation shares it.
|
|
50
|
+
- **Skills.** Bundled skills reach agents whose preset includes the skill tool. The `minimal` preset does not, so agents using it do not see them.
|
|
51
|
+
- **Logs.** Desktop shows no plugin output. To debug the OpenViking tools, set `OPENVIKING_DEBUG=1` and `OPENVIKING_DEBUG_LOG=<file>`, then reopen Desktop.
|
|
52
|
+
|
|
53
|
+
## Uninstall
|
|
54
|
+
|
|
55
|
+
Plugins → OpenViking Memory → Uninstall. Or, with Desktop quit:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop rm @openviking/dsh-memory-plugin
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Full documentation: [DeepSeek Harness integration](https://github.com/volcengine/OpenViking/blob/main/docs/en/agent-integrations/17-dsh.md) and the [bundle README](https://github.com/volcengine/OpenViking/tree/main/examples/dsh-memory-plugin).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# OpenViking 记忆
|
|
2
|
+
|
|
3
|
+
基于 [OpenViking](https://github.com/volcengine/OpenViking) 服务端,为 DeepSeek Harness 提供长期记忆。DeepSeek Harness Desktop 和 `dsh web` 均可使用。
|
|
4
|
+
|
|
5
|
+
- **召回**:每个带有你输入的步骤开始前,把相关记忆加入对话。
|
|
6
|
+
- **捕获**:把你的消息和助手的回复发送到 OpenViking,并提交为记忆。
|
|
7
|
+
- **工具与技能**:模型可以使用 OpenViking 工具(`mcp__openviking__search`、`read`、`remember`、`add_skill` 等),以及 `openviking-memory`、`openviking-skills`、`ov-experience-memory` 三个技能。
|
|
8
|
+
- **URI 保护**:文件工具指向 `viking://` 路径时会被拦下,并提示改用对应的 OpenViking 工具;`bash` 或 `pwsh` 命令里带 `viking://` URI 时照常执行,只给模型附一条提示。
|
|
9
|
+
|
|
10
|
+
## 1. 连接服务端
|
|
11
|
+
|
|
12
|
+
写 `~/.openviking/ovcli.conf`,填 `url` 和 `api_key`(trusted 模式的服务端还要填 `account` 和 `user`)。如果用的是 `http://127.0.0.1:1933` 上无鉴权的本地服务端,这步可以跳过。参见 [部署指南 → CLI](https://github.com/volcengine/OpenViking/blob/main/docs/zh/guides/03-deployment.md#cli)。
|
|
13
|
+
|
|
14
|
+
在 Desktop 里建议用 `ovcli.conf` 而不是环境变量。macOS 上 Desktop 只在启动时读取一次登录 shell 的环境变量;Windows 上使用用户环境变量。改动后需要彻底退出并重新打开 Desktop。
|
|
15
|
+
|
|
16
|
+
## 2. 安装
|
|
17
|
+
|
|
18
|
+
**通过安装器(macOS)**:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -fsSL https://openviking.ai/install | bash
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
安装器需要 Node.js 18 或更高版本。它会找到 Desktop,作为 `dsh-desktop` harness 安装进去。运行前先打开一次 Desktop 让它创建 profile,安装期间用 ⌘Q 退出 Desktop:关闭窗口只是把它隐藏起来。
|
|
25
|
+
|
|
26
|
+
**通过插件页面(手动;macOS、Windows 和 `dsh web`)**:插件 → 添加插件 → 输入 `@openviking/dsh-memory-plugin` → 安装,开关未打开时再打开。这一步只添加插件,不会询问 OpenViking 凭据,所以要先完成第 1 步。
|
|
27
|
+
|
|
28
|
+
**通过 Desktop 自带的 `dsh` 命令(macOS)**:先打开一次 Desktop,用 ⌘Q 退出,再运行:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop add @openviking/dsh-memory-plugin
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
通过 npm 安装的 `dsh` 无法管理 `desktop` profile。用命令行安装后,重新打开 Desktop。
|
|
35
|
+
|
|
36
|
+
## 3. 验证
|
|
37
|
+
|
|
38
|
+
新开一个对话。顶部附近应能看到 OpenViking 上下文块,模型的工具列表里应有 `mcp__openviking__*`。问一件之前对话里的事,确认召回生效。
|
|
39
|
+
|
|
40
|
+
在终端里检查安装结果(需先退出 Desktop):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop ls
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 注意事项
|
|
47
|
+
|
|
48
|
+
- **没有设置页面。** 通过 `ovcli.conf` 或 `OPENVIKING_*` 环境变量配置插件。行为参数也可以写进 `~/.dsh/profiles/desktop/cordis.patch.yml` 的 `openviking-memory-runtime` 条目。恢复对话框里的“禁用第三方插件、备份 profile patch 并重启”会重置 profile 的插件,并把这个文件挪作备份,所以长期设置请放在 `ovcli.conf`,恢复后记得重新启用插件。
|
|
49
|
+
- **工具身份。** 自动召回和捕获按每个对话所在的项目区分。OpenViking 工具每个 profile 只启动一次,工作目录是 profile 目录,所以在 `OPENVIKING_RECALL_PEER_SCOPE=actor` 下它们没有项目 peer。需要时在 `ovcli.conf` 里设置 `actor_peer_id`,或设置 `OPENVIKING_PEER_ID`。这个 peer 也会取代每个对话的项目 peer 用于召回和捕获,因此所有对话共用它。
|
|
50
|
+
- **技能。** 内置技能只对预设里带技能工具的 agent 可见。`minimal` 预设不带,用它的 agent 看不到这些技能。
|
|
51
|
+
- **日志。** Desktop 不显示插件输出。要排查 OpenViking 工具,设置 `OPENVIKING_DEBUG=1` 和 `OPENVIKING_DEBUG_LOG=<文件>`,然后重新打开 Desktop。
|
|
52
|
+
|
|
53
|
+
## 卸载
|
|
54
|
+
|
|
55
|
+
插件 → OpenViking 记忆 → 卸载。或者在退出 Desktop 后运行:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop rm @openviking/dsh-memory-plugin
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
完整文档:[DeepSeek Harness 集成](https://github.com/volcengine/OpenViking/blob/main/docs/zh/agent-integrations/17-dsh.md) 和 [插件 README](https://github.com/volcengine/OpenViking/tree/main/examples/dsh-memory-plugin)。
|
package/index.mjs
CHANGED
|
@@ -86,8 +86,10 @@ export function apply(ctx, input = {}) {
|
|
|
86
86
|
]);
|
|
87
87
|
if (signal.aborted) return decision;
|
|
88
88
|
const additions = [profile, recall].filter(Boolean);
|
|
89
|
+
// Keep the rest of the decision: goal-round-driver marks its entries with
|
|
90
|
+
// `startsRequestSeries`, and rebuilding the object would drop that flag.
|
|
89
91
|
return additions.length > 0
|
|
90
|
-
? {
|
|
92
|
+
? { ...decision, messages: [...decision.messages, ...additions] }
|
|
91
93
|
: decision;
|
|
92
94
|
}, { prepend: true });
|
|
93
95
|
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "OpenViking Memory",
|
|
4
|
-
"description": "Long-term semantic memory for DeepSeek Harness: auto-recall before each step, session capture and commit, viking:// URI protection, and the OpenViking MCP tools and skills."
|
|
4
|
+
"description": "Long-term semantic memory for DeepSeek Harness: auto-recall before each step, session capture and commit, viking:// URI protection, and the OpenViking MCP tools and skills.",
|
|
5
|
+
"detail": "docs/DETAIL.md"
|
|
5
6
|
}
|
|
6
7
|
}
|
package/locale/zh.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "OpenViking 记忆",
|
|
4
|
-
"description": "为 DeepSeek Harness 提供长期语义记忆:每步自动召回、会话采集与提交、viking:// URI 保护,以及 OpenViking MCP 工具与技能。"
|
|
4
|
+
"description": "为 DeepSeek Harness 提供长期语义记忆:每步自动召回、会话采集与提交、viking:// URI 保护,以及 OpenViking MCP 工具与技能。",
|
|
5
|
+
"detail": "docs/DETAIL.zh.md"
|
|
5
6
|
}
|
|
6
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openviking/dsh-memory-plugin",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.30",
|
|
4
4
|
"description": "OpenViking memory and context bundle for DeepSeek Harness",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"shared/",
|
|
28
28
|
"skills/",
|
|
29
29
|
"locale/",
|
|
30
|
+
"docs/",
|
|
30
31
|
"cordis.patch.yml",
|
|
31
32
|
"README.md"
|
|
32
33
|
],
|
package/runtime.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
listPending,
|
|
8
8
|
replayPending,
|
|
9
9
|
} from "./shared/pending-queue.mjs";
|
|
10
|
-
import { redactPayload } from "./shared/redact.mjs";
|
|
11
10
|
import { isRetryableFailure } from "./shared/retryable.mjs";
|
|
12
11
|
import { resolveEffectivePeerId } from "./shared/workspace-peer.mjs";
|
|
13
12
|
import {
|
|
@@ -151,13 +150,9 @@ export class OpenVikingRuntime {
|
|
|
151
150
|
capture(session, event) {
|
|
152
151
|
const state = this.stateFor(session);
|
|
153
152
|
if (!isCaptureEnabled(state.config)) return;
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
153
|
+
const payload = captureEvent(event, state.config, state.toolNames);
|
|
154
|
+
if (!payload) return;
|
|
156
155
|
this.enqueueWrite(state, async () => {
|
|
157
|
-
// Redacted once, inside the write chain so a slow scan never blocks the
|
|
158
|
-
// host's event: the direct POST and the pending queue both carry this
|
|
159
|
-
// copy, never the plaintext.
|
|
160
|
-
const payload = redactPayload(captured, { tier: state.config.captureRedact });
|
|
161
156
|
if (state.hasPendingWrites) {
|
|
162
157
|
await this.enqueuePendingMessage(state, payload);
|
|
163
158
|
return;
|
|
@@ -176,7 +171,6 @@ export class OpenVikingRuntime {
|
|
|
176
171
|
state.ovSessionId,
|
|
177
172
|
payload,
|
|
178
173
|
state.config.peerId,
|
|
179
|
-
{ redacted: true },
|
|
180
174
|
);
|
|
181
175
|
if (isRetryableFailure(response)) {
|
|
182
176
|
await this.enqueuePendingMessage(state, payload);
|
|
@@ -271,10 +265,7 @@ export class OpenVikingRuntime {
|
|
|
271
265
|
async enqueuePending(state, type, payload) {
|
|
272
266
|
const createdAt = Math.max(Date.now(), state.pendingCreatedAt + 1);
|
|
273
267
|
state.pendingCreatedAt = createdAt;
|
|
274
|
-
const result = await enqueue(type, state.ovSessionId, payload, {
|
|
275
|
-
createdAt,
|
|
276
|
-
redactTier: state.config.captureRedact,
|
|
277
|
-
});
|
|
268
|
+
const result = await enqueue(type, state.ovSessionId, payload, { createdAt });
|
|
278
269
|
if (result.ok) {
|
|
279
270
|
// Log the latch transition only: every message that follows while the
|
|
280
271
|
// latch holds takes the cheap enqueue path, so this fires once per
|
|
@@ -343,7 +334,7 @@ export class OpenVikingRuntime {
|
|
|
343
334
|
await replayPending(
|
|
344
335
|
(path, init) => this.client.fetchJSON(path, init),
|
|
345
336
|
(stage, data) => this.log(stage, data),
|
|
346
|
-
|
|
337
|
+
options,
|
|
347
338
|
);
|
|
348
339
|
}
|
|
349
340
|
|
package/shared/config-schema.mjs
CHANGED
|
@@ -24,11 +24,6 @@
|
|
|
24
24
|
* harness per-harness default, where one genuinely differs
|
|
25
25
|
* min/max clamped, not rejected: a config file must not kill a hook
|
|
26
26
|
* values the enum's members; anything else falls back to the default
|
|
27
|
-
* boolValues enum only: the member a boolean-ish value (`true`/`yes`/`on`,
|
|
28
|
-
* `false`/`no`/`off`/`0`) stands for, so `false` can mean "off"
|
|
29
|
-
* unknownToDefault
|
|
30
|
-
* enum only: a value naming no member resolves to the default
|
|
31
|
-
* instead of leaving the lower layer's value standing
|
|
32
27
|
* env OPENVIKING_* variable, the highest-priority layer
|
|
33
28
|
* aliases older spellings, honoured forever; the doctor names them
|
|
34
29
|
* workspace dotted key in `.openviking/config.json`
|
|
@@ -156,23 +151,6 @@ export const KNOBS = [
|
|
|
156
151
|
// tool results, so it captures them by default.
|
|
157
152
|
{ name: "captureToolResults", type: "bool", default: false, harness: { dsh: true }, env: "OPENVIKING_CAPTURE_TOOL_RESULTS", capability: "capture" },
|
|
158
153
|
{ name: "captureFilters", type: "list", default: [], env: "OPENVIKING_CAPTURE_FILTERS", capability: "capture" },
|
|
159
|
-
// Secret redaction of every captured payload before it is sent or queued
|
|
160
|
-
// (lib/redact.mjs); the tiers are REDACT_TIERS there, in the same order.
|
|
161
|
-
// User-level only — deliberately no `workspace` key, so a repository's
|
|
162
|
-
// `.openviking/config.json` cannot switch redaction off for whoever opens it.
|
|
163
|
-
// `false` means off rather than "unparseable"; a tier this build does not
|
|
164
|
-
// know (a newer config read by an older plugin) means the default, never a
|
|
165
|
-
// weaker value left over from a lower layer.
|
|
166
|
-
{
|
|
167
|
-
name: "captureRedact",
|
|
168
|
-
type: "enum",
|
|
169
|
-
values: ["off", "standard", "strict"],
|
|
170
|
-
default: "standard",
|
|
171
|
-
boolValues: { true: "standard", false: "off" },
|
|
172
|
-
unknownToDefault: true,
|
|
173
|
-
env: "OPENVIKING_CAPTURE_REDACT",
|
|
174
|
-
capability: "capture",
|
|
175
|
-
},
|
|
176
154
|
// 0 means "derive from timeoutMs": a write gets a longer budget than a read.
|
|
177
155
|
{ name: "captureTimeoutMs", type: "int", default: 0, min: 0, max: 600000, env: "OPENVIKING_CAPTURE_TIMEOUT_MS", capability: "capture" },
|
|
178
156
|
{ name: "commitTokenThreshold", type: "int", default: 20000, min: 1000, max: 1000000, env: "OPENVIKING_COMMIT_TOKEN_THRESHOLD", workspace: "capture.commit_token_threshold", capability: "capture" },
|
|
@@ -284,6 +262,7 @@ export const WORKSPACE_RANGES = Object.fromEntries(
|
|
|
284
262
|
*/
|
|
285
263
|
export const HARNESS_KEYS = {
|
|
286
264
|
claudeCode: "claude_code",
|
|
265
|
+
codebuddy: "codebuddy",
|
|
287
266
|
codex: "codex",
|
|
288
267
|
cursor: "cursor",
|
|
289
268
|
kimicode: "kimicode",
|
|
@@ -339,16 +318,8 @@ export function coerceKnobValue(knob, raw, fallback) {
|
|
|
339
318
|
return knob.max === undefined ? floored : Math.min(knob.max, floored);
|
|
340
319
|
}
|
|
341
320
|
case "enum": {
|
|
342
|
-
const values = knob.values || [];
|
|
343
321
|
const word = String(raw ?? "").trim();
|
|
344
|
-
|
|
345
|
-
if (knob.boolValues) {
|
|
346
|
-
const lower = word.toLowerCase();
|
|
347
|
-
if (values.includes(lower)) return lower;
|
|
348
|
-
if (raw === true || TRUE_WORDS.includes(lower)) return knob.boolValues.true;
|
|
349
|
-
if (raw === false || FALSE_WORDS.includes(lower)) return knob.boolValues.false;
|
|
350
|
-
}
|
|
351
|
-
return knob.unknownToDefault && word ? knobDefault(knob) : fallback;
|
|
322
|
+
return (knob.values || []).includes(word) ? word : fallback;
|
|
352
323
|
}
|
|
353
324
|
// A peer template may be one string or an ordered list of fallbacks, and
|
|
354
325
|
// `String([a, b])` would quietly turn the list into one bogus template.
|
package/shared/pending-queue.mjs
CHANGED
|
@@ -11,12 +11,6 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Each file contains: { type, sessionId, payload, createdAt, retries, dedupKey }
|
|
13
13
|
*
|
|
14
|
-
* An addMessage payload is secret-redacted (lib/redact.mjs) before it is
|
|
15
|
-
* written, and again before it is replayed: the queue is plaintext on disk, and
|
|
16
|
-
* an entry written by an older plugin, or by a caller that skipped redaction,
|
|
17
|
-
* must not reach the server as it was. Redaction is idempotent, so replaying an
|
|
18
|
-
* already-redacted entry sends the same bytes.
|
|
19
|
-
*
|
|
20
14
|
* Config (env vars):
|
|
21
15
|
* OPENVIKING_PENDING_DIR pending queue directory
|
|
22
16
|
* (default: ~/.openviking/pending)
|
|
@@ -31,7 +25,6 @@ import { mkdir, readdir, readFile, rename, writeFile, unlink, stat, chmod, utime
|
|
|
31
25
|
import { createHash } from "node:crypto";
|
|
32
26
|
import { join } from "node:path";
|
|
33
27
|
import { homedir } from "node:os";
|
|
34
|
-
import { redactPayload } from "./redact.mjs";
|
|
35
28
|
import { isRetryableFailure } from "./retryable.mjs";
|
|
36
29
|
|
|
37
30
|
const DEFAULT_MAX_RETRIES = 3;
|
|
@@ -191,21 +184,8 @@ async function recoverStaleProcessing(dir) {
|
|
|
191
184
|
* @param {object} payload - the data that failed to send
|
|
192
185
|
* @param {object} options
|
|
193
186
|
* @param {number} options.createdAt - optional queue timestamp override
|
|
194
|
-
* @param {string} [options.redactTier] - the captureRedact tier an addMessage
|
|
195
|
-
* payload is redacted under before it is written (default "standard")
|
|
196
|
-
* @param {boolean} [options.redacted] - the caller already redacted `payload`
|
|
197
|
-
* under its configured tier, so it is written as it is
|
|
198
187
|
*/
|
|
199
188
|
export async function enqueue(type, sessionId, payload, options = {}) {
|
|
200
|
-
if (type === "addMessage" && options.redacted !== true) {
|
|
201
|
-
try {
|
|
202
|
-
payload = redactPayload(payload, { tier: options.redactTier });
|
|
203
|
-
} catch (err) {
|
|
204
|
-
// redactPayload fails closed on its own; this is the last line before
|
|
205
|
-
// plaintext would be written to disk, so refuse rather than write it.
|
|
206
|
-
return { ok: false, error: `redaction failed: ${err?.message || String(err)}` };
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
189
|
const dir = getPendingDir();
|
|
210
190
|
const now = Number.isFinite(options.createdAt) ? options.createdAt : Date.now();
|
|
211
191
|
const dedupKey = makeDedupKey(type, sessionId, payload);
|
|
@@ -398,8 +378,6 @@ export async function cleanStale() {
|
|
|
398
378
|
* a background drainer can keep retrying a transient failure without
|
|
399
379
|
* burning the session-start retry budget. Exhausted and non-retryable
|
|
400
380
|
* entries are deleted exactly as in the default mode.
|
|
401
|
-
* @param {string} [options.redactTier="standard"] - the captureRedact tier
|
|
402
|
-
* every replayed addMessage payload is redacted under before it is sent
|
|
403
381
|
* @returns {{ replayed: number, failed: number, skipped: number, deferred: number }}
|
|
404
382
|
*/
|
|
405
383
|
export async function replayPending(fetchJSON, log, options = {}) {
|
|
@@ -444,7 +422,7 @@ export async function replayPending(fetchJSON, log, options = {}) {
|
|
|
444
422
|
if (entry.type === "addMessage") {
|
|
445
423
|
res = await fetchJSON(`/api/v1/sessions/${encodedSid}/messages`, {
|
|
446
424
|
method: "POST",
|
|
447
|
-
body: JSON.stringify(
|
|
425
|
+
body: JSON.stringify(entry.payload),
|
|
448
426
|
});
|
|
449
427
|
} else if (entry.type === "commitSession") {
|
|
450
428
|
res = await fetchJSON(`/api/v1/sessions/${encodedSid}/commit`, {
|
package/shared/uri-guard.mjs
CHANGED
|
@@ -167,7 +167,9 @@ export const DEFAULT_TOOL_HINTS = {
|
|
|
167
167
|
// Tools whose argument is a command line rather than a location. A viking:// URI
|
|
168
168
|
// in one is data (an `ov` argument, an HTTP payload, a grep pattern) as often as
|
|
169
169
|
// a path the model hoped to open, so the command runs and the model gets a notice.
|
|
170
|
-
|
|
170
|
+
// Only a name the host's hint table lists is guarded at all, so the PowerShell
|
|
171
|
+
// names reach no host that does not map them (dsh's Windows `pwsh` tool does).
|
|
172
|
+
const SHELL_TOOL_NAMES = new Set(["bash", "shell", "runcommand", "pwsh", "powershell"]);
|
|
171
173
|
|
|
172
174
|
// `pattern` is where glob looks but what grep looks for: a grep for the text
|
|
173
175
|
// "viking://" in a local tree is not a path. The generic sweep still reaches
|
package/uri-guard.mjs
CHANGED
|
@@ -24,6 +24,11 @@ const GUARDED_TOOLS = {
|
|
|
24
24
|
tool: `${mcp("read")} or ${mcp("search")}`,
|
|
25
25
|
example: uri => `${mcp("read")}(uris="${uri}")`,
|
|
26
26
|
},
|
|
27
|
+
// On Windows DSH mounts `pwsh` in place of `bash`; its command line is also `command`.
|
|
28
|
+
pwsh: {
|
|
29
|
+
tool: `${mcp("read")} or ${mcp("search")}`,
|
|
30
|
+
example: uri => `${mcp("read")}(uris="${uri}")`,
|
|
31
|
+
},
|
|
27
32
|
edit: {
|
|
28
33
|
tool: uri => (isSkillUri(uri) ? mcp("add_skill") : mcp("edit")),
|
|
29
34
|
example: uri =>
|