@everme/dsh 0.6.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 +39 -0
- package/cordis.patch.yml +5 -0
- package/index.js +190 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @everme/dsh
|
|
2
|
+
|
|
3
|
+
Native EverMe lifecycle integration for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
4
|
+
|
|
5
|
+
The Cordis plugin complements `@everme/memory-mcp`:
|
|
6
|
+
|
|
7
|
+
- `agent/pre-step` performs query-specific recall before the first model step of each turn.
|
|
8
|
+
- `session/event` captures the completed DSH turn, including tool calls and results, and writes it through `/mem/agent-memory`.
|
|
9
|
+
- `session/flush` waits for pending EverMe writes so DSH persistence checkpoints do not race the memory upload.
|
|
10
|
+
- Recall and save failures degrade open: DSH continues without blocking the user turn.
|
|
11
|
+
- The MCP server remains available through `npx -y @everme/memory-mcp@latest` for explicit `mem_context`, `mem_search`, `mem_save_fact`, and `mem_save_turn` tool calls.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Use EverCLI so the native plugin, MCP server, Cordis patch, and credentials stay in sync:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @everme/cli
|
|
19
|
+
evercli auth login
|
|
20
|
+
evercli plugin install dsh
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`@everme/dsh` declares a native DSH bundle. EverCLI runs the latest official launcher through `npx --yes @deepseek-ai/dsh@latest`, refreshes the profile dependency with `plugin --profile web add @everme/dsh@latest`, and configures the MCP client to start `@everme/memory-mcp@latest` through `npx` at runtime. EverCLI manages only the MCP block in `~/.dsh/profiles/web/cordis.patch.yml` and the credential block in `~/.dsh/.env`. Restart DSH after installation if its patch watcher has not reloaded the configuration.
|
|
24
|
+
|
|
25
|
+
## Cordis entry
|
|
26
|
+
|
|
27
|
+
The package exports the standard Cordis plugin surface:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
export const name = "everme";
|
|
31
|
+
export const inject = ["agents"];
|
|
32
|
+
export function apply(ctx, config) {}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Credentials are read from DSH's layered environment (`EVERME_API_BASE`, `EVERME_AGENT_ID`, and `EVERME_AGENT_TOKEN`). Do not put tokens directly in `cordis.patch.yml`.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
Apache-2.0.
|
package/cordis.patch.yml
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import {
|
|
3
|
+
assertConfigUsable,
|
|
4
|
+
createClient,
|
|
5
|
+
redactError,
|
|
6
|
+
resolveConfig,
|
|
7
|
+
runInject,
|
|
8
|
+
saveAgentMemory,
|
|
9
|
+
toText,
|
|
10
|
+
} from "@everme/agent-sdk";
|
|
11
|
+
|
|
12
|
+
export const name = "everme";
|
|
13
|
+
export const inject = ["agents"];
|
|
14
|
+
|
|
15
|
+
export function apply(ctx, config = {}) {
|
|
16
|
+
installEverMeHooks(ctx, config);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function installEverMeHooks(ctx, config = {}, dependencies = {}) {
|
|
20
|
+
const log = createLogger(ctx, dependencies.log);
|
|
21
|
+
const resolved = dependencies.config || resolveConfig(config);
|
|
22
|
+
try {
|
|
23
|
+
assertConfigUsable(resolved);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
log.warn(`[everme] native hooks disabled: ${safeError(error)}`);
|
|
26
|
+
return { enabled: false };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const client = dependencies.client || createClient(resolved, log);
|
|
30
|
+
const recall = dependencies.runInject || runInject;
|
|
31
|
+
const save = dependencies.saveAgentMemory || saveAgentMemory;
|
|
32
|
+
const makeUserMessage = dependencies.createUserMessage || createUserMessage;
|
|
33
|
+
const pending = new WeakMap();
|
|
34
|
+
|
|
35
|
+
ctx.on("agent/pre-step", async ({ messages, step, signal }, next) => {
|
|
36
|
+
const decision = await next();
|
|
37
|
+
if (decision.kind !== "enter" || signal.aborted || step !== 1) return decision;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const prompt = humanPrompt(decision.messages || messages);
|
|
41
|
+
if (!prompt) return decision;
|
|
42
|
+
const result = await recall({ input: { prompt }, client, config: resolved, log });
|
|
43
|
+
if (!result?.block) return decision;
|
|
44
|
+
return {
|
|
45
|
+
kind: "enter",
|
|
46
|
+
messages: [
|
|
47
|
+
...decision.messages,
|
|
48
|
+
makeUserMessage({
|
|
49
|
+
content: [{ type: "text", text: result.block }],
|
|
50
|
+
source: {
|
|
51
|
+
kind: "plugin",
|
|
52
|
+
plugin: name,
|
|
53
|
+
form: "snapshot",
|
|
54
|
+
sections: [{ name: "everme-recall", text: result.block }],
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
log.warn(`[everme] recall degraded open: ${safeError(error)}`);
|
|
61
|
+
return decision;
|
|
62
|
+
}
|
|
63
|
+
}, { prepend: true });
|
|
64
|
+
|
|
65
|
+
ctx.on("session/event", (session, event) => {
|
|
66
|
+
if (event?.type !== "turn/end") return;
|
|
67
|
+
const messages = collectTurnMessages(session, event.data?.turn, event.seq);
|
|
68
|
+
if (!messages.length) return;
|
|
69
|
+
enqueue(pending, session, async () => {
|
|
70
|
+
await save(client, {
|
|
71
|
+
conversationId: String(session.id),
|
|
72
|
+
messages,
|
|
73
|
+
flush: true,
|
|
74
|
+
}, log);
|
|
75
|
+
}, log);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
ctx.on("session/flush", async (session) => {
|
|
79
|
+
await (pending.get(session) || Promise.resolve());
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return { enabled: true, pending };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function collectTurnMessages(session, turn, endSeq = Number.POSITIVE_INFINITY) {
|
|
86
|
+
if (!session || !Number.isSafeInteger(turn) || !Array.isArray(session.events)) return [];
|
|
87
|
+
const events = session.events;
|
|
88
|
+
let startIndex = -1;
|
|
89
|
+
let endIndex = events.length;
|
|
90
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
91
|
+
const event = events[index];
|
|
92
|
+
if (event?.seq > endSeq) continue;
|
|
93
|
+
if (event?.type === "turn/end" && event.data?.turn === turn) {
|
|
94
|
+
endIndex = index + 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (event?.type === "turn/start" && event.data?.turn === turn) {
|
|
98
|
+
startIndex = index;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (startIndex < 0) return [];
|
|
103
|
+
|
|
104
|
+
const messages = [];
|
|
105
|
+
for (const event of events.slice(startIndex + 1, endIndex)) {
|
|
106
|
+
const converted = convertSessionEvent(event);
|
|
107
|
+
if (converted) messages.push(converted);
|
|
108
|
+
}
|
|
109
|
+
return messages;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function convertSessionEvent(event) {
|
|
113
|
+
if (event?.type === "user/message") {
|
|
114
|
+
const message = event.data;
|
|
115
|
+
if (message?.source?.kind !== "user") return null;
|
|
116
|
+
const content = toText(message.content);
|
|
117
|
+
return content ? { role: "user", content, timestamp: event.time } : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (event?.type === "assistant/message") {
|
|
121
|
+
const content = normalizeAssistantContent(event.data?.message?.content);
|
|
122
|
+
return content.length ? { role: "assistant", content, timestamp: event.time } : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (event?.type === "tool/result") {
|
|
126
|
+
const block = event.data?.message?.content?.find((item) => item?.type === "tool-result");
|
|
127
|
+
if (!block?.toolCallId) return null;
|
|
128
|
+
return {
|
|
129
|
+
role: "tool",
|
|
130
|
+
toolCallId: String(block.toolCallId),
|
|
131
|
+
content: block.content || [],
|
|
132
|
+
timestamp: event.time,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeAssistantContent(content) {
|
|
140
|
+
const normalized = [];
|
|
141
|
+
for (const block of Array.isArray(content) ? content : []) {
|
|
142
|
+
if (block?.type === "text" && block.text) {
|
|
143
|
+
normalized.push({ type: "text", text: block.text });
|
|
144
|
+
} else if (block?.type === "tool-call" && block.id) {
|
|
145
|
+
normalized.push({
|
|
146
|
+
type: "toolCall",
|
|
147
|
+
id: String(block.id),
|
|
148
|
+
name: block.name || "unknown",
|
|
149
|
+
arguments: block.arguments || "{}",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return normalized;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function humanPrompt(messages) {
|
|
157
|
+
return (Array.isArray(messages) ? messages : [])
|
|
158
|
+
.filter((message) => message?.source?.kind === "user")
|
|
159
|
+
.map((message) => toText(message.content))
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.join("\n\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function enqueue(pending, session, operation, log) {
|
|
165
|
+
const previous = pending.get(session) || Promise.resolve();
|
|
166
|
+
const current = previous
|
|
167
|
+
.catch(() => {})
|
|
168
|
+
.then(operation)
|
|
169
|
+
.catch((error) => {
|
|
170
|
+
log.warn(`[everme] save degraded open: ${safeError(error)}`);
|
|
171
|
+
});
|
|
172
|
+
pending.set(session, current);
|
|
173
|
+
return current;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function createLogger(ctx, override) {
|
|
177
|
+
if (override) return override;
|
|
178
|
+
return {
|
|
179
|
+
info(line) {
|
|
180
|
+
ctx?.logger?.info?.(line);
|
|
181
|
+
},
|
|
182
|
+
warn(line) {
|
|
183
|
+
ctx?.logger?.warn?.(line);
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function safeError(error) {
|
|
189
|
+
return redactError(error instanceof Error ? error.message : String(error));
|
|
190
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@everme/dsh",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Native EverMe lifecycle hooks for DeepSeek Harness.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"main": "./index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js",
|
|
14
|
+
"cordis.patch.yml",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.19.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node --test tests/plugin.test.js"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"evermind",
|
|
25
|
+
"everme",
|
|
26
|
+
"deepseek",
|
|
27
|
+
"dsh",
|
|
28
|
+
"memory",
|
|
29
|
+
"cordis",
|
|
30
|
+
"hooks"
|
|
31
|
+
],
|
|
32
|
+
"homepage": "https://everme.evermind.ai",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/EverMind-AI/EverMe.git",
|
|
36
|
+
"directory": "plugins/dsh"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/EverMind-AI/EverMe/issues"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@everme/agent-sdk": "^0.6.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6 <0.2.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6"
|
|
53
|
+
},
|
|
54
|
+
"dsh": {
|
|
55
|
+
"bundle": {
|
|
56
|
+
"patch": "./cordis.patch.yml"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|