@openviking/dsh-memory-plugin 0.1.0
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 +129 -0
- package/capture.mjs +104 -0
- package/client.mjs +206 -0
- package/config.mjs +161 -0
- package/cordis.patch.yml +9 -0
- package/index.mjs +56 -0
- package/lifecycle.mjs +8 -0
- package/package.json +60 -0
- package/runtime.mjs +316 -0
- package/shared/capture-utils.mjs +515 -0
- package/shared/credentials.mjs +228 -0
- package/shared/debug-log.mjs +58 -0
- package/shared/pending-queue.mjs +451 -0
- package/shared/plugin-config.mjs +90 -0
- package/shared/profile-inject.mjs +294 -0
- package/shared/recall-compress-core.mjs +195 -0
- package/shared/recall-core.mjs +609 -0
- package/shared/retryable.mjs +9 -0
- package/shared/session-model.mjs +57 -0
- package/shared/setup-wizard.mjs +111 -0
- package/shared/uri-guard.mjs +56 -0
- package/shared/workspace-peer.mjs +16 -0
- package/tools.mjs +246 -0
- package/uri-guard.mjs +51 -0
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openviking/dsh-memory-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenViking memory and context bundle for DeepSeek Harness",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.mjs",
|
|
12
|
+
"client.mjs",
|
|
13
|
+
"config.mjs",
|
|
14
|
+
"capture.mjs",
|
|
15
|
+
"lifecycle.mjs",
|
|
16
|
+
"runtime.mjs",
|
|
17
|
+
"tools.mjs",
|
|
18
|
+
"uri-guard.mjs",
|
|
19
|
+
"shared/",
|
|
20
|
+
"cordis.patch.yml",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"dsh": {
|
|
24
|
+
"bundle": {
|
|
25
|
+
"patch": "./cordis.patch.yml"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "node --test *.test.mjs"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
33
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.6"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
37
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.6"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": "^22.19.0 || >=24"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/volcengine/OpenViking.git",
|
|
48
|
+
"directory": "examples/dsh-memory-plugin"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"deepseek-harness",
|
|
52
|
+
"dsh",
|
|
53
|
+
"openviking",
|
|
54
|
+
"memory",
|
|
55
|
+
"context",
|
|
56
|
+
"auto-recall",
|
|
57
|
+
"auto-capture"
|
|
58
|
+
],
|
|
59
|
+
"license": "Apache-2.0"
|
|
60
|
+
}
|
package/runtime.mjs
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { buildProfileBlock } from "./shared/profile-inject.mjs";
|
|
3
|
+
import { buildRecallBlock } from "./shared/recall-core.mjs";
|
|
4
|
+
import { deriveHarnessSessionId } from "./shared/session-model.mjs";
|
|
5
|
+
import {
|
|
6
|
+
dequeue,
|
|
7
|
+
enqueue,
|
|
8
|
+
listPending,
|
|
9
|
+
replayPending,
|
|
10
|
+
} from "./shared/pending-queue.mjs";
|
|
11
|
+
import { isRetryableFailure } from "./shared/retryable.mjs";
|
|
12
|
+
import { resolveEffectivePeerId } from "./shared/workspace-peer.mjs";
|
|
13
|
+
import {
|
|
14
|
+
captureEvent,
|
|
15
|
+
OPENVIKING_PLUGIN_SOURCE,
|
|
16
|
+
promptText,
|
|
17
|
+
} from "./capture.mjs";
|
|
18
|
+
|
|
19
|
+
export class OpenVikingRuntime {
|
|
20
|
+
constructor(client, config, logger = console) {
|
|
21
|
+
this.client = client;
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.states = new Map();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
stateFor(session) {
|
|
28
|
+
let state = this.states.get(session.id);
|
|
29
|
+
if (state) return state;
|
|
30
|
+
const cwd = session.header?.cwd || process.cwd();
|
|
31
|
+
const peerId = resolveEffectivePeerId({
|
|
32
|
+
cfg: {
|
|
33
|
+
peerId: this.config.explicitPeerId,
|
|
34
|
+
workspacePeer: this.config.workspacePeer,
|
|
35
|
+
},
|
|
36
|
+
cwd,
|
|
37
|
+
}).peerId;
|
|
38
|
+
state = {
|
|
39
|
+
dshSessionId: String(session.id),
|
|
40
|
+
ovSessionId: deriveHarnessSessionId("dsh-", String(session.id)),
|
|
41
|
+
config: { ...this.config, peerId },
|
|
42
|
+
ready: false,
|
|
43
|
+
profileBlock: "",
|
|
44
|
+
profileDelivered: false,
|
|
45
|
+
toolNames: new Map(),
|
|
46
|
+
writes: Promise.resolve(),
|
|
47
|
+
initializationRetryable: false,
|
|
48
|
+
hasPendingWrites: false,
|
|
49
|
+
pendingCreatedAt: 0,
|
|
50
|
+
disposing: null,
|
|
51
|
+
};
|
|
52
|
+
this.states.set(session.id, state);
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async initialize(agent) {
|
|
57
|
+
const state = this.stateFor(agent.session);
|
|
58
|
+
return this.ensureState(state);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async ensureState(state) {
|
|
62
|
+
if (state.ready) return state;
|
|
63
|
+
if (state.initializing) return state.initializing;
|
|
64
|
+
state.initializing = this.initializeState(state).finally(() => {
|
|
65
|
+
state.initializing = null;
|
|
66
|
+
});
|
|
67
|
+
return state.initializing;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async initializeState(state) {
|
|
71
|
+
state.initializationRetryable = false;
|
|
72
|
+
const health = await this.client.healthResult();
|
|
73
|
+
if (!health.ok) {
|
|
74
|
+
state.initializationRetryable = isRetryableFailure(health);
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
const ensured = await this.client.ensureSessionResult(
|
|
78
|
+
state.ovSessionId,
|
|
79
|
+
state.config.peerId,
|
|
80
|
+
);
|
|
81
|
+
if (
|
|
82
|
+
!ensured.ok
|
|
83
|
+
&& !(ensured.status === 409 && ensured.error?.code === "ALREADY_EXISTS")
|
|
84
|
+
) {
|
|
85
|
+
state.initializationRetryable = isRetryableFailure(ensured);
|
|
86
|
+
return state;
|
|
87
|
+
}
|
|
88
|
+
await replayPending(
|
|
89
|
+
(path, init) => this.client.fetchJSON(path, init),
|
|
90
|
+
(stage, data) => this.log(stage, data),
|
|
91
|
+
);
|
|
92
|
+
await this.refreshPendingState(state);
|
|
93
|
+
const profile = await buildProfileBlock(
|
|
94
|
+
(path, init, options) => this.client.fetchJSON(path, init, options),
|
|
95
|
+
state.config.profileTokenBudget,
|
|
96
|
+
state.config.peerId,
|
|
97
|
+
);
|
|
98
|
+
state.profileBlock = profile?.block
|
|
99
|
+
? [
|
|
100
|
+
'<openviking-context source="profile">',
|
|
101
|
+
profile.block,
|
|
102
|
+
"</openviking-context>",
|
|
103
|
+
].join("\n")
|
|
104
|
+
: "";
|
|
105
|
+
state.ready = true;
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async profileMessage(agent) {
|
|
110
|
+
const state = await this.initialize(agent);
|
|
111
|
+
if (!state.ready || !state.profileBlock || state.profileDelivered) return null;
|
|
112
|
+
state.profileDelivered = true;
|
|
113
|
+
return pluginMessage(state.profileBlock, "instructions");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async recallMessage(agent, messages) {
|
|
117
|
+
const state = await this.initialize(agent);
|
|
118
|
+
if (!state.ready) return null;
|
|
119
|
+
const query = promptText(messages);
|
|
120
|
+
if (query.length < state.config.minQueryLength) return null;
|
|
121
|
+
const block = await buildRecallBlock(
|
|
122
|
+
(path, init, options) => this.client.fetchJSON(path, init, options),
|
|
123
|
+
state.config,
|
|
124
|
+
query,
|
|
125
|
+
{
|
|
126
|
+
actorPeerId: state.config.peerId,
|
|
127
|
+
sessionId: state.ovSessionId,
|
|
128
|
+
log: (stage, data) => this.log(stage, data),
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
return block ? pluginMessage(block, "recall") : null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
capture(session, event) {
|
|
135
|
+
const state = this.stateFor(session);
|
|
136
|
+
if (!state.config.syncTurns) return;
|
|
137
|
+
const payload = captureEvent(event, state.config, state.toolNames);
|
|
138
|
+
if (!payload) return;
|
|
139
|
+
this.enqueueWrite(state, async () => {
|
|
140
|
+
if (state.hasPendingWrites) {
|
|
141
|
+
await this.enqueuePendingMessage(state, payload);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (!state.ready && !(await this.ensureState(state)).ready) {
|
|
145
|
+
if (state.initializationRetryable) {
|
|
146
|
+
await this.enqueuePendingMessage(state, payload);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (state.hasPendingWrites) {
|
|
151
|
+
await this.enqueuePendingMessage(state, payload);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const response = await this.client.addMessage(
|
|
155
|
+
state.ovSessionId,
|
|
156
|
+
payload,
|
|
157
|
+
state.config.peerId,
|
|
158
|
+
);
|
|
159
|
+
if (isRetryableFailure(response)) {
|
|
160
|
+
await this.enqueuePendingMessage(state, payload);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
maybeCommit(session, event) {
|
|
166
|
+
if (event.type !== "turn/end") return;
|
|
167
|
+
const state = this.stateFor(session);
|
|
168
|
+
this.enqueueWrite(state, async () => {
|
|
169
|
+
if (state.hasPendingWrites) return;
|
|
170
|
+
if (!state.ready && !(await this.ensureState(state)).ready) return;
|
|
171
|
+
const metadata = await this.client.getSession(
|
|
172
|
+
state.ovSessionId,
|
|
173
|
+
state.config.peerId,
|
|
174
|
+
);
|
|
175
|
+
if (Number(metadata?.pending_tokens || 0) < state.config.commitTokenThreshold) return;
|
|
176
|
+
const response = await this.client.commitSession(
|
|
177
|
+
state.ovSessionId,
|
|
178
|
+
state.config.peerId,
|
|
179
|
+
);
|
|
180
|
+
this.log("commit", {
|
|
181
|
+
sessionId: state.ovSessionId,
|
|
182
|
+
ok: response.ok,
|
|
183
|
+
trace_id: response.result?.trace_id || response.traceId,
|
|
184
|
+
error: response.ok ? undefined : response.error?.message || response.error?.code,
|
|
185
|
+
});
|
|
186
|
+
if (isRetryableFailure(response)) {
|
|
187
|
+
await this.enqueueFinalCommit(state, {
|
|
188
|
+
keep_recent_count: state.config.commitKeepRecentCount,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
dispose(session) {
|
|
195
|
+
const state = this.states.get(session.id);
|
|
196
|
+
if (!state) return;
|
|
197
|
+
if (state.disposing) return state.disposing;
|
|
198
|
+
state.disposing = (async () => {
|
|
199
|
+
this.enqueueWrite(state, async () => {
|
|
200
|
+
const commitPayload = {
|
|
201
|
+
keep_recent_count: state.config.commitKeepRecentCount,
|
|
202
|
+
};
|
|
203
|
+
if (state.hasPendingWrites) {
|
|
204
|
+
await this.enqueueFinalCommit(state, commitPayload);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!state.ready && !(await this.ensureState(state)).ready) return;
|
|
208
|
+
const response = await this.client.commitSession(
|
|
209
|
+
state.ovSessionId,
|
|
210
|
+
state.config.peerId,
|
|
211
|
+
{ timeoutMs: Math.min(3000, Number(state.config.requestTimeoutMs) || 3000) },
|
|
212
|
+
);
|
|
213
|
+
this.log("shutdown_commit", {
|
|
214
|
+
sessionId: state.ovSessionId,
|
|
215
|
+
ok: response.ok,
|
|
216
|
+
trace_id: response.result?.trace_id || response.traceId,
|
|
217
|
+
});
|
|
218
|
+
if (isRetryableFailure(response)) {
|
|
219
|
+
await this.enqueueFinalCommit(state, commitPayload);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
await state.writes;
|
|
224
|
+
} finally {
|
|
225
|
+
if (this.states.get(session.id) === state) this.states.delete(session.id);
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
return state.disposing;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async disposeAll() {
|
|
232
|
+
await Promise.all([...this.states.values()].map(state => this.dispose({
|
|
233
|
+
id: state.dshSessionId,
|
|
234
|
+
})));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
enqueueWrite(state, operation) {
|
|
238
|
+
state.writes = state.writes
|
|
239
|
+
.then(operation)
|
|
240
|
+
.catch(error => this.log("write_error", {
|
|
241
|
+
sessionId: state.ovSessionId,
|
|
242
|
+
error: error instanceof Error ? error.message : String(error),
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async enqueuePending(state, type, payload) {
|
|
247
|
+
const createdAt = Math.max(Date.now(), state.pendingCreatedAt + 1);
|
|
248
|
+
state.pendingCreatedAt = createdAt;
|
|
249
|
+
const result = await enqueue(type, state.ovSessionId, payload, { createdAt });
|
|
250
|
+
if (result.ok) state.hasPendingWrites = true;
|
|
251
|
+
if (!result.ok) {
|
|
252
|
+
this.log("pending_enqueue_error", {
|
|
253
|
+
sessionId: state.ovSessionId,
|
|
254
|
+
type,
|
|
255
|
+
error: result.error,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async enqueueFinalCommit(state, payload) {
|
|
262
|
+
await this.removePendingCommits(state);
|
|
263
|
+
return this.enqueuePending(state, "commitSession", payload);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async enqueuePendingMessage(state, payload) {
|
|
267
|
+
const result = await this.enqueuePending(state, "addMessage", payload);
|
|
268
|
+
if (result.ok) await this.removePendingCommits(state);
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async removePendingCommits(state) {
|
|
273
|
+
const pending = await listPending();
|
|
274
|
+
for (const item of pending) {
|
|
275
|
+
if (
|
|
276
|
+
item.entry?.type === "commitSession"
|
|
277
|
+
&& item.entry.sessionId === state.ovSessionId
|
|
278
|
+
) {
|
|
279
|
+
await dequeue(item.filename);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async refreshPendingState(state) {
|
|
285
|
+
const pending = (await listPending()).filter(
|
|
286
|
+
item => item.entry?.sessionId === state.ovSessionId,
|
|
287
|
+
);
|
|
288
|
+
state.hasPendingWrites = pending.length > 0;
|
|
289
|
+
state.pendingCreatedAt = pending.reduce(
|
|
290
|
+
(latest, item) => Math.max(latest, Number(item.entry?.createdAt || 0)),
|
|
291
|
+
state.pendingCreatedAt,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async flush(session) {
|
|
296
|
+
const state = this.states.get(session.id);
|
|
297
|
+
if (state) await state.writes;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
log(stage, data) {
|
|
301
|
+
this.logger?.debug?.(`[openviking:dsh] ${stage} ${JSON.stringify(data)}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function pluginMessage(content, form) {
|
|
306
|
+
// dsh's own constructor: identity, normalization, and any future Message
|
|
307
|
+
// invariants come from the pinned peer instead of a hand-built object.
|
|
308
|
+
return createUserMessage({
|
|
309
|
+
content: [{ type: "text", text: content }],
|
|
310
|
+
source: {
|
|
311
|
+
kind: "plugin",
|
|
312
|
+
plugin: OPENVIKING_PLUGIN_SOURCE,
|
|
313
|
+
form,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
}
|