@everme/claude-code 0.4.0 → 0.4.2
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/commands/everme-help.md +5 -3
- package/commands/recall.md +8 -7
- package/hooks/scripts/mcp-server.js +321 -42
- package/package.json +3 -3
- package/skills/memory-tools.md +18 -16
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "everme",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Automatic memory recall for Claude Code through the EverMe gateway. Saves and recalls per-session context using your EverMe account credentials.",
|
|
13
|
-
"version": "0.4.
|
|
13
|
+
"version": "0.4.2",
|
|
14
14
|
"homepage": "https://everme.evermind.ai",
|
|
15
15
|
"license": "Apache-2.0"
|
|
16
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "everme",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "EverMe — automatic memory recall for Claude Code. Recalls relevant context from past sessions before each prompt and saves new turns through the EverMe gateway.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "EverMind AI",
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Automatic memory recall + persistence for Claude Code, backed by the EverMe gate
|
|
|
11
11
|
|
|
12
12
|
Plus:
|
|
13
13
|
|
|
14
|
-
- **MCP server** exposing `
|
|
14
|
+
- **MCP server** exposing the canonical `mem_search` / `mem_context` / `mem_save_fact` / `mem_save_turn` tools for explicit recall and saves.
|
|
15
15
|
- **Slash commands** `/recall <q>` and `/everme-help`.
|
|
16
16
|
- **Skill** (`memory-tools`) that tells Claude when/how to use the search tool.
|
|
17
17
|
|
|
@@ -76,7 +76,7 @@ In a new Claude Code session:
|
|
|
76
76
|
/recall the postgres composite index decision
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
…should call `
|
|
79
|
+
…should call `mem_search`, summarise hits, and cite memory subjects.
|
|
80
80
|
|
|
81
81
|
## Files
|
|
82
82
|
|
|
@@ -89,7 +89,7 @@ hooks/scripts/inject-memories.js UserPromptSubmit handler
|
|
|
89
89
|
hooks/scripts/store-memories.js Stop handler
|
|
90
90
|
hooks/scripts/session-start.js SessionStart handler
|
|
91
91
|
hooks/scripts/session-summary.js SessionEnd handler
|
|
92
|
-
hooks/scripts/mcp-server.js MCP server (
|
|
92
|
+
hooks/scripts/mcp-server.js MCP server (canonical mem_* tools)
|
|
93
93
|
hooks/scripts/lib/adapter.js Claude Code stdin/transcript/stdout adapter
|
|
94
94
|
hooks/scripts/lib/run-hook.js thin shared-runtime entry helper
|
|
95
95
|
hooks/scripts/lib/config.js Env-var resolution (emk vs evt)
|
package/commands/everme-help.md
CHANGED
|
@@ -14,7 +14,7 @@ Auth: EVERME_API_KEY (account emk_*) — recall-only mode
|
|
|
14
14
|
Gateway: EVERME_API_BASE — defaults to https://api.everme.evermind.ai
|
|
15
15
|
|
|
16
16
|
Hooks:
|
|
17
|
-
SessionStart → loads
|
|
17
|
+
SessionStart → loads the durable Profile snapshot
|
|
18
18
|
UserPromptSubmit → recalls relevant memories before each prompt
|
|
19
19
|
Stop → saves the last raw turn through /mem/agent-memory
|
|
20
20
|
SessionEnd → no persistence; Stop owns runtime writes
|
|
@@ -24,6 +24,8 @@ Slash:
|
|
|
24
24
|
/everme-help — this card
|
|
25
25
|
|
|
26
26
|
MCP tools:
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
mem_search — hybrid search across all memory buckets
|
|
28
|
+
mem_context — durable Profile snapshot only
|
|
29
|
+
mem_save_fact — save a durable user fact to the Profile path
|
|
30
|
+
mem_save_turn — save a reusable task trajectory
|
|
29
31
|
```
|
package/commands/recall.md
CHANGED
|
@@ -8,10 +8,10 @@ arguments:
|
|
|
8
8
|
|
|
9
9
|
# EverMe · recall
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Use the canonical EverMe MCP tools:
|
|
12
12
|
|
|
13
|
-
- `
|
|
14
|
-
- `
|
|
13
|
+
- `mem_search` — hybrid search across episodic memories, profile entries, agent cases/skills, and the recent raw transcript.
|
|
14
|
+
- `mem_context` — durable Profile snapshot only; it does not search past conversations and is not needed for this command.
|
|
15
15
|
|
|
16
16
|
## Query
|
|
17
17
|
|
|
@@ -19,8 +19,9 @@ You have access to two MCP tools backed by the EverMe gateway:
|
|
|
19
19
|
|
|
20
20
|
## Instructions
|
|
21
21
|
|
|
22
|
-
1. Call `
|
|
23
|
-
2. If the
|
|
22
|
+
1. Call `mem_search` with a short version of the query. Start with `topK: 10`.
|
|
23
|
+
2. If the returned memories are clearly relevant, summarize them briefly and use them to answer or guide the next action.
|
|
24
24
|
3. If results are weak or empty, retry once with broader keywords. If still nothing useful, say so explicitly — do not fabricate context.
|
|
25
|
-
4.
|
|
26
|
-
5.
|
|
25
|
+
4. Treat rows under "Recent unextracted transcript" as provisional, not as established facts or confirmed decisions.
|
|
26
|
+
5. When citing a memory, mention its subject or session id when available so the user can trace it through `evercli` or the EverMe Web UI.
|
|
27
|
+
6. NEVER paste an entire long memory body verbatim; quote only the salient parts.
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MCP server bundled with the Claude Code plugin. Exposes the
|
|
4
|
-
* EverMe
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* UserPromptSubmit hook has already done implicit recall.
|
|
3
|
+
* MCP server bundled with the Claude Code plugin. Exposes the canonical
|
|
4
|
+
* EverMe four-tool catalogue (mem_search / mem_context / mem_save_turn /
|
|
5
|
+
* mem_save_fact — the same public ABI as @everme/memory-mcp and the Go
|
|
6
|
+
* hosted /mcp surface).
|
|
8
7
|
*
|
|
9
8
|
* Wire format: MCP stdio transport (JSON-RPC 2.0 framed by line).
|
|
10
9
|
* We hand-roll the tiny subset Claude Code uses rather than pulling
|
|
11
10
|
* in @modelcontextprotocol/sdk — keeps the install fast (no npm
|
|
12
|
-
* install required) and the dependency surface minimal.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* everme_context — POST /api/v1/mem/context (server-rendered prompt block)
|
|
11
|
+
* install required) and the dependency surface minimal. The canonical
|
|
12
|
+
* tools are a thin adapter over @everme/agent-sdk helpers — this
|
|
13
|
+
* package must NOT import @everme/memory-mcp (host packages depend on
|
|
14
|
+
* agent-sdk only).
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
17
|
import { createInterface } from "readline";
|
|
@@ -21,8 +19,11 @@ import { createRequire } from "node:module";
|
|
|
21
19
|
import {
|
|
22
20
|
buildMemoryPrompt,
|
|
23
21
|
createClient,
|
|
22
|
+
getContext,
|
|
24
23
|
searchMemory,
|
|
25
|
-
|
|
24
|
+
saveAgentMemory,
|
|
25
|
+
savePersonalMemory,
|
|
26
|
+
AGENT_MEMORY_ROLES,
|
|
26
27
|
redactError,
|
|
27
28
|
EvermeError,
|
|
28
29
|
} from "@everme/agent-sdk";
|
|
@@ -52,29 +53,229 @@ function getClient() {
|
|
|
52
53
|
return client;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
// Instructions returned on initialize — Claude Code splices them into the
|
|
57
|
+
// system prompt. Mirrors @everme/memory-mcp's EVERME_MCP_INSTRUCTIONS with
|
|
58
|
+
// the Claude-Code-specific note that native hooks already inject
|
|
59
|
+
// <everme_profile> / <everme_recall> and save turns automatically.
|
|
60
|
+
const INSTRUCTIONS = [
|
|
61
|
+
"EverMe memory is connected. This plugin's native hooks already inject a",
|
|
62
|
+
"<everme_profile> block at session start and a <everme_recall> block before",
|
|
63
|
+
"each prompt, and they save the conversation automatically — so the tools",
|
|
64
|
+
"below are for the cases the hooks do not cover. Call them AUTONOMOUSLY when",
|
|
65
|
+
"a trigger fires; never wait for the user to say \"remember\" or \"recall\".",
|
|
66
|
+
"1. The <everme_recall> block is missing, empty, or clearly irrelevant AND the user references earlier conversations, decisions, conventions, or previously solved problems — call `mem_search` with a SHORT query. Do not repeat an identical query in the same turn.",
|
|
67
|
+
"2. The user states a durable fact about themselves (a preference, habit, trait, long-term goal, or decision) — call `mem_save_fact` immediately. Only `extracted:true` / `profileUpdated:true` means the profile really updated; on `no_extraction` do NOT tell the user the fact was remembered, and do not auto-retry.",
|
|
68
|
+
"3. A task was solved in a way worth reusing and the trajectory is NOT already captured by the hooks — call `mem_save_turn` with the COMPLETE trajectory. It feeds episodic / case / skill extraction; chat-dual-write backends may also update the Profile, reported by profileUpdated.",
|
|
69
|
+
"4. No <everme_profile> block was injected this session — call `mem_context` once. It returns the durable Profile ONLY (no search, no episodes).",
|
|
70
|
+
].join("\n");
|
|
71
|
+
|
|
55
72
|
const TOOLS = [
|
|
56
73
|
{
|
|
57
|
-
name: "
|
|
74
|
+
name: "mem_search",
|
|
58
75
|
description:
|
|
59
|
-
"Search EverMe
|
|
76
|
+
"Search EverMe memory for entries relevant to a free-text query. " +
|
|
77
|
+
"Returns the top-K matching entries (episodic, profile, agent " +
|
|
78
|
+
"cases/skills, recent raw transcript) rendered as markdown; rows " +
|
|
79
|
+
"under the provisional transcript header are not yet extracted and " +
|
|
80
|
+
"must not be quoted as established facts.\n\n" +
|
|
81
|
+
"Call this proactively — without being asked — whenever the user " +
|
|
82
|
+
"references prior conversations, earlier decisions, project " +
|
|
83
|
+
"conventions, or previously solved problems (\"what did we say " +
|
|
84
|
+
"about X\", \"remember when…\", \"like last time\", \"did we fix " +
|
|
85
|
+
"this before\", \"continue where we left off\").\n\n" +
|
|
86
|
+
"Skip the call when the host already injected a non-empty, relevant " +
|
|
87
|
+
"<everme_recall> block this turn, and do not repeat an identical " +
|
|
88
|
+
"query within the same turn.\n\n" +
|
|
89
|
+
"`query` is KEYWORDS ONLY — the topic, not the conversation. Two to " +
|
|
90
|
+
"eight words, under ~100 characters, no sentences copied from the " +
|
|
91
|
+
"transcript. Never pass the user's whole message, a file, a log, a " +
|
|
92
|
+
"diff, or your own reasoning: the search embeds whatever you send, " +
|
|
93
|
+
"so boilerplate crowds out the topic and the results get worse. " +
|
|
94
|
+
"Good: \"oauth token rotation\". Bad: the last three turns pasted " +
|
|
95
|
+
"in. Rely on the default topK of 10; only raise it if a first " +
|
|
96
|
+
"search genuinely missed.",
|
|
60
97
|
inputSchema: {
|
|
61
98
|
type: "object",
|
|
62
99
|
properties: {
|
|
63
|
-
query: {
|
|
64
|
-
|
|
100
|
+
query: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description:
|
|
103
|
+
"Keywords naming the topic to recall — two to eight words, " +
|
|
104
|
+
"under ~100 characters. Not a sentence from the transcript, " +
|
|
105
|
+
"not the user's whole message, not a pasted file or log.",
|
|
106
|
+
},
|
|
107
|
+
topK: { type: "integer", description: "Max entries to return", default: 10 },
|
|
65
108
|
},
|
|
66
109
|
required: ["query"],
|
|
67
110
|
},
|
|
68
111
|
},
|
|
69
112
|
{
|
|
70
|
-
name: "
|
|
113
|
+
name: "mem_context",
|
|
114
|
+
description:
|
|
115
|
+
"Read the current user's durable Profile snapshot ONLY. This tool " +
|
|
116
|
+
"never performs semantic search and never returns episodic memories, " +
|
|
117
|
+
"raw messages, agent cases, or agent skills.\n\n" +
|
|
118
|
+
"Call it ONCE at the start of a session, and only when no " +
|
|
119
|
+
"<everme_profile> block was injected. Do NOT use it as a fallback for " +
|
|
120
|
+
"recalling past decisions, old sessions, or task context — that is " +
|
|
121
|
+
"mem_search's job.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
query: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description:
|
|
128
|
+
"Deprecated and ignored — mem_context never performs semantic " +
|
|
129
|
+
"search. Kept for backwards compatibility only.",
|
|
130
|
+
},
|
|
131
|
+
forceRefresh: {
|
|
132
|
+
type: "boolean",
|
|
133
|
+
default: false,
|
|
134
|
+
description: "Bypass the server-side profile cache and re-read the upstream profile.",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "mem_save_turn",
|
|
141
|
+
description:
|
|
142
|
+
"Persist a conversation trajectory in realtime via /mem/agent-memory. " +
|
|
143
|
+
"Use sessionKey as conversationId.\n\n" +
|
|
144
|
+
"Call this when a task was solved in a way worth reusing AND the " +
|
|
145
|
+
"trajectory is not already captured by the plugin's automatic " +
|
|
146
|
+
"transcript save. Pass the COMPLETE round-trip — messages: [{role, " +
|
|
147
|
+
"content, timestamp?, toolCalls?, toolCallId?}] — EverOS only " +
|
|
148
|
+
"extracts agent_case / agent_skill from trajectories carrying the " +
|
|
149
|
+
"full tool round-trip.\n\n" +
|
|
150
|
+
"By default flush=true: extraction into episodic / agent_case / " +
|
|
151
|
+
"agent_skill runs right away. Pass flush=false for append-only " +
|
|
152
|
+
"accumulation. The primary trajectory path extracts episodic / case / " +
|
|
153
|
+
"skill memory; chat-dual-write backends may also update the user's " +
|
|
154
|
+
"Profile. Check profileUpdated for the derived profile verdict, and " +
|
|
155
|
+
"use mem_save_fact for deliberate durable user facts.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
role: {
|
|
160
|
+
type: "string",
|
|
161
|
+
enum: ["user", "assistant", "tool"],
|
|
162
|
+
description: "Role for the single-message form. Ignored when messages[] is set.",
|
|
163
|
+
},
|
|
164
|
+
text: { type: "string", description: "Content for the single-message form. Ignored when messages[] is set." },
|
|
165
|
+
timestamp: { type: "integer", description: "Unix milliseconds; defaults to now. Single-message form only." },
|
|
166
|
+
toolCallId: { type: "string", description: "Required when role=tool. Single-message form only." },
|
|
167
|
+
toolCalls: {
|
|
168
|
+
type: "array",
|
|
169
|
+
description:
|
|
170
|
+
"Tool invocations made by an assistant message. Required when " +
|
|
171
|
+
"the assistant called tools — without this the tool round-trip " +
|
|
172
|
+
"can never be reconstructed downstream and EverOS will not " +
|
|
173
|
+
"produce agent_case / agent_skill from the turn.",
|
|
174
|
+
items: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
id: { type: "string" },
|
|
178
|
+
name: { type: "string" },
|
|
179
|
+
arguments: { type: "string", description: "JSON-encoded tool arguments (stringified, not an object)." },
|
|
180
|
+
},
|
|
181
|
+
required: ["id", "name", "arguments"],
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
messages: {
|
|
185
|
+
type: "array",
|
|
186
|
+
description:
|
|
187
|
+
"Multi-message trajectory. Preferred for recording a complete " +
|
|
188
|
+
"user → assistant{tool_use} → tool{tool_result} → assistant cycle.",
|
|
189
|
+
items: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
role: { type: "string", enum: ["user", "assistant", "tool"] },
|
|
193
|
+
content: { description: "String text, or array of content items (text / image / doc)." },
|
|
194
|
+
timestamp: { type: "integer" },
|
|
195
|
+
toolCallId: { type: "string", description: "Required when role=tool." },
|
|
196
|
+
toolCalls: {
|
|
197
|
+
type: "array",
|
|
198
|
+
items: {
|
|
199
|
+
type: "object",
|
|
200
|
+
properties: {
|
|
201
|
+
id: { type: "string" },
|
|
202
|
+
name: { type: "string" },
|
|
203
|
+
arguments: { type: "string" },
|
|
204
|
+
},
|
|
205
|
+
required: ["id", "name", "arguments"],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
required: ["role"],
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
sessionKey: { type: "string", description: "Session id; defaults to 'default'." },
|
|
213
|
+
flush: {
|
|
214
|
+
type: "boolean",
|
|
215
|
+
default: true,
|
|
216
|
+
description:
|
|
217
|
+
"true (default) = trigger EverOS extraction into " +
|
|
218
|
+
"episodic_memory / agent_case / agent_skill after writing; " +
|
|
219
|
+
"false = append-only, messages are searchable as raw_messages " +
|
|
220
|
+
"only and extraction is deferred until a later flush. The " +
|
|
221
|
+
"default flipped from false to true because callers rarely " +
|
|
222
|
+
"issued an explicit follow-up flush, leaving trajectories " +
|
|
223
|
+
"permanently stuck as raw_messages with zero case/skill.",
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "mem_save_fact",
|
|
71
230
|
description:
|
|
72
|
-
"
|
|
231
|
+
"Persist a durable fact about the USER (a preference, habit, trait, " +
|
|
232
|
+
"or decision) via the long-term PROFILE write path — the block loaded " +
|
|
233
|
+
"at the start of every session.\n\n" +
|
|
234
|
+
"Call this proactively, without being asked, the moment the user " +
|
|
235
|
+
"states something true about themselves that should outlive this " +
|
|
236
|
+
"conversation (\"I love summer\", \"sign my docs as Alice\").\n\n" +
|
|
237
|
+
"This is the direct profile-producing sibling of mem_save_turn — " +
|
|
238
|
+
"mem_save_turn primarily records trajectories, though chat-dual-write " +
|
|
239
|
+
"backends may derive a profile update. With flush=true (default) " +
|
|
240
|
+
"the call runs the synchronous materialise path and returns the real " +
|
|
241
|
+
"EverOS verdict: only `extracted:true` / `profileUpdated:true` " +
|
|
242
|
+
"confirms the fact reached the profile. `status:\"no_extraction\"` " +
|
|
243
|
+
"means the profile did NOT update — never claim the fact was " +
|
|
244
|
+
"remembered in that case, and do not auto-retry.",
|
|
73
245
|
inputSchema: {
|
|
74
246
|
type: "object",
|
|
75
247
|
properties: {
|
|
76
|
-
|
|
77
|
-
|
|
248
|
+
fact: {
|
|
249
|
+
type: "string",
|
|
250
|
+
description:
|
|
251
|
+
"A single user-stated fact, recorded as one user-role message. " +
|
|
252
|
+
"Ignored when messages[] is set.",
|
|
253
|
+
},
|
|
254
|
+
messages: {
|
|
255
|
+
type: "array",
|
|
256
|
+
description:
|
|
257
|
+
"Explicit user/assistant turns. Tool roles are not accepted on " +
|
|
258
|
+
"this path. Preferred when you want to capture both the user's " +
|
|
259
|
+
"statement and your acknowledgement.",
|
|
260
|
+
items: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
role: { type: "string", enum: ["user", "assistant"] },
|
|
264
|
+
content: { description: "String text, or array of content items." },
|
|
265
|
+
timestamp: { type: "integer", description: "Unix milliseconds; defaults to now." },
|
|
266
|
+
},
|
|
267
|
+
required: ["role"],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
sessionKey: { type: "string", description: "Session id; defaults to 'default'." },
|
|
271
|
+
flush: {
|
|
272
|
+
type: "boolean",
|
|
273
|
+
default: true,
|
|
274
|
+
description:
|
|
275
|
+
"true (default) = issue EverOS flush and return its verdict; " +
|
|
276
|
+
"false = skip extraction entirely (fact accepted but not in the " +
|
|
277
|
+
"profile block).",
|
|
278
|
+
},
|
|
78
279
|
},
|
|
79
280
|
},
|
|
80
281
|
},
|
|
@@ -90,6 +291,7 @@ const handlers = {
|
|
|
90
291
|
protocolVersion,
|
|
91
292
|
capabilities: { tools: { listChanged: false } },
|
|
92
293
|
serverInfo: { name: "everme", version: PKG_VERSION },
|
|
294
|
+
instructions: INSTRUCTIONS,
|
|
93
295
|
};
|
|
94
296
|
},
|
|
95
297
|
"tools/list": () => ({ tools: TOOLS }),
|
|
@@ -101,14 +303,8 @@ const handlers = {
|
|
|
101
303
|
}
|
|
102
304
|
try {
|
|
103
305
|
switch (name) {
|
|
104
|
-
case "
|
|
105
|
-
|
|
106
|
-
// path inject-memories.js uses for the auto-recall hook,
|
|
107
|
-
// and the same shape @everme/memory-mcp's mem_search tool
|
|
108
|
-
// returns. Earlier `JSON.stringify(res, null, 2)` forced the
|
|
109
|
-
// host LLM to peel a JSON envelope and decode escaped
|
|
110
|
-
// newlines before any of the section bullets were readable.
|
|
111
|
-
const topK = Math.min(Number(args.topK) || 10, 25);
|
|
306
|
+
case "mem_search": {
|
|
307
|
+
const topK = Math.min(Number(args.topK) || 10, 50);
|
|
112
308
|
const res = await searchMemory(getClient(), { query: String(args.query || ""), topK });
|
|
113
309
|
const body = buildMemoryPrompt(res, { wrapInCodeBlock: false });
|
|
114
310
|
const header = `## EverMe search results for "${String(args.query || "")}"`;
|
|
@@ -118,23 +314,86 @@ const handlers = {
|
|
|
118
314
|
: `${header}\n\n_(no matching memories)_`;
|
|
119
315
|
return ok(redactError(text));
|
|
120
316
|
}
|
|
121
|
-
case "
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const res = await getClient().request("POST", "/mem/context", {});
|
|
130
|
-
// renderProfileBlock returns "" when profile exists but has no
|
|
131
|
-
// facts/traits yet (new account). Check the rendered output, not
|
|
132
|
-
// just the wrapper object, so the empty case yields a fallback
|
|
133
|
-
// message instead of an empty tool result.
|
|
134
|
-
const rendered = res?.profile ? renderProfileBlock(res.profile) : "";
|
|
135
|
-
const text = rendered || "_(no profile available — your EverMe account has no extracted memories yet)_";
|
|
317
|
+
case "mem_context": {
|
|
318
|
+
// Profile-only: `query` is accepted for compat but ignored.
|
|
319
|
+
const ctx = await getContext(
|
|
320
|
+
getClient(),
|
|
321
|
+
"",
|
|
322
|
+
{ forceRefresh: args.forceRefresh === true },
|
|
323
|
+
);
|
|
324
|
+
const text = ctx?.context || "_(no profile available — your EverMe account has no extracted memories yet)_";
|
|
136
325
|
return ok(redactError(text));
|
|
137
326
|
}
|
|
327
|
+
case "mem_save_turn": {
|
|
328
|
+
let messages;
|
|
329
|
+
if (Array.isArray(args.messages) && args.messages.length) {
|
|
330
|
+
messages = args.messages.map(normaliseTurnMessage);
|
|
331
|
+
} else {
|
|
332
|
+
messages = [normaliseTurnMessage({
|
|
333
|
+
role: args.role || AGENT_MEMORY_ROLES.USER,
|
|
334
|
+
content: args.text,
|
|
335
|
+
timestamp: args.timestamp,
|
|
336
|
+
toolCallId: args.toolCallId,
|
|
337
|
+
toolCalls: args.toolCalls,
|
|
338
|
+
})];
|
|
339
|
+
}
|
|
340
|
+
const res = await saveAgentMemory(getClient(), {
|
|
341
|
+
conversationId: args.sessionKey || "default",
|
|
342
|
+
messages,
|
|
343
|
+
flush: args.flush !== false,
|
|
344
|
+
});
|
|
345
|
+
return okJson({
|
|
346
|
+
saved: !!res,
|
|
347
|
+
accepted: !!res,
|
|
348
|
+
status: res?.status || null,
|
|
349
|
+
messageCount: res?.messageCount || 0,
|
|
350
|
+
flushed: !!res?.flushed,
|
|
351
|
+
profileStatus: res?.personalStatus || null,
|
|
352
|
+
profileUpdated: !!res?.personalExtracted,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
case "mem_save_fact": {
|
|
356
|
+
let messages;
|
|
357
|
+
if (Array.isArray(args.messages) && args.messages.length) {
|
|
358
|
+
const bad = args.messages.find(
|
|
359
|
+
(m) => m?.role !== AGENT_MEMORY_ROLES.USER && m?.role !== AGENT_MEMORY_ROLES.ASSISTANT,
|
|
360
|
+
);
|
|
361
|
+
if (bad) {
|
|
362
|
+
return errResp(
|
|
363
|
+
`mem_save_fact accepts only 'user' or 'assistant' roles; got ${JSON.stringify(bad?.role)}. ` +
|
|
364
|
+
"The personal-memory path does not record tool turns — use mem_save_turn for trajectories.",
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
messages = args.messages.map((m) => ({
|
|
368
|
+
role: m?.role,
|
|
369
|
+
content: m?.content !== undefined ? m.content : m?.text,
|
|
370
|
+
timestamp: Number(m?.timestamp) || Date.now(),
|
|
371
|
+
}));
|
|
372
|
+
} else if (typeof args.fact === "string" && args.fact.trim()) {
|
|
373
|
+
messages = [{ role: AGENT_MEMORY_ROLES.USER, content: args.fact, timestamp: Date.now() }];
|
|
374
|
+
} else {
|
|
375
|
+
return errResp("mem_save_fact requires either `fact` or a non-empty `messages` array");
|
|
376
|
+
}
|
|
377
|
+
const res = await savePersonalMemory(getClient(), {
|
|
378
|
+
conversationId: args.sessionKey || "default",
|
|
379
|
+
messages,
|
|
380
|
+
flush: args.flush !== false,
|
|
381
|
+
});
|
|
382
|
+
if (!res) {
|
|
383
|
+
return errResp("mem_save_fact wrote nothing — every message had empty content after normalization");
|
|
384
|
+
}
|
|
385
|
+
return okJson({
|
|
386
|
+
saved: true,
|
|
387
|
+
accepted: true,
|
|
388
|
+
status: res?.status || null,
|
|
389
|
+
messageCount: res?.messageCount || 0,
|
|
390
|
+
flushed: !!res?.flushed,
|
|
391
|
+
extracted: !!res?.extracted,
|
|
392
|
+
// profileUpdated aliases extracted — the only signal that the
|
|
393
|
+
// fact really materialised into the profile.
|
|
394
|
+
profileUpdated: !!res?.extracted,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
138
397
|
default:
|
|
139
398
|
return errResp(`unknown tool: ${name}`);
|
|
140
399
|
}
|
|
@@ -148,10 +407,30 @@ const handlers = {
|
|
|
148
407
|
function ok(text) {
|
|
149
408
|
return { content: [{ type: "text", text: String(text ?? "") }] };
|
|
150
409
|
}
|
|
410
|
+
function okJson(data) {
|
|
411
|
+
return { content: [{ type: "text", text: JSON.stringify(data ?? {}, null, 2) }] };
|
|
412
|
+
}
|
|
151
413
|
function errResp(msg) {
|
|
152
414
|
return { isError: true, content: [{ type: "text", text: `error: ${msg}` }] };
|
|
153
415
|
}
|
|
154
416
|
|
|
417
|
+
// normaliseTurnMessage coerces an LLM-provided message into the SDK
|
|
418
|
+
// agent-memory shape — accepts both legacy {role, text} and canonical
|
|
419
|
+
// {role, content, toolCalls, toolCallId} forms. Mirrors the equivalent
|
|
420
|
+
// helper in @everme/memory-mcp (kept local: host packages must not
|
|
421
|
+
// import each other).
|
|
422
|
+
function normaliseTurnMessage(m) {
|
|
423
|
+
const role = m?.role || AGENT_MEMORY_ROLES.USER;
|
|
424
|
+
const out = { role, timestamp: Number(m?.timestamp) || Date.now() };
|
|
425
|
+
if (m?.content !== undefined) out.content = m.content;
|
|
426
|
+
else if (m?.text !== undefined) out.content = String(m.text);
|
|
427
|
+
if (Array.isArray(m?.toolCalls) && m.toolCalls.length) out.toolCalls = m.toolCalls;
|
|
428
|
+
if (role === AGENT_MEMORY_ROLES.TOOL && (m?.toolCallId || m?.tool_call_id)) {
|
|
429
|
+
out.toolCallId = String(m.toolCallId || m.tool_call_id);
|
|
430
|
+
}
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
|
|
155
434
|
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
156
435
|
rl.on("line", async (line) => {
|
|
157
436
|
let req;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everme/claude-code",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "EverMe native plugin for Claude Code — automatic memory recall via SessionStart/UserPromptSubmit/Stop/SessionEnd hooks, plus /recall slash + bundled MCP server.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"node": ">=18.0.0"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "node --test tests/redact.test.js tests/config.test.js tests/transcript.test.js tests/hooks.test.js tests/mcp-server.test.js"
|
|
11
|
+
"test": "node --test tests/redact.test.js tests/config.test.js tests/transcript.test.js tests/hooks.test.js tests/mcp-server.test.js tests/skills.test.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
".claude-plugin/",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"README.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@everme/agent-sdk": "^0.4.
|
|
24
|
+
"@everme/agent-sdk": "^0.4.2"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"evermind",
|
package/skills/memory-tools.md
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Use EverMe memory proactively when the user refers to previous conversations, earlier decisions, "last time", "remember when", existing project conventions, or previously solved errors, and save durable user preferences, habits, and decisions the moment they are stated. Do not repeat a search when a non-empty <everme_recall> block already exists.
|
|
3
3
|
alwaysInclude: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# EverMe Memory Tools
|
|
7
7
|
|
|
8
|
-
You have
|
|
8
|
+
You have four canonical MCP tools for memory EverMe persists across past Claude Code sessions:
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- `
|
|
10
|
+
Recall:
|
|
11
|
+
- `mem_search` — semantic + keyword hybrid search over the user's memory store (episodic, profile, agent cases/skills, recent raw transcript). Rows under "Recent unextracted transcript" are provisional, not established facts.
|
|
12
|
+
- `mem_context` — the user's durable Profile snapshot ONLY. It never searches and never returns episodes; do not use it to recall past decisions or task context.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Write:
|
|
15
|
+
- `mem_save_fact` — save a durable user fact (preference, habit, trait, long-term decision). Call it proactively the moment the user states one ("以后文档签名用 Alice", "I hate Friday meetings") — do NOT wait for the user to say "remember this".
|
|
16
|
+
- `mem_save_turn` — persist a complete task trajectory worth reusing. Chat-dual-write backends may also update the user's Profile; check `profileUpdated`. Use `mem_save_fact` for a deliberate durable fact.
|
|
14
17
|
|
|
15
|
-
##
|
|
18
|
+
## Dedupe protocol (hooks come first)
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
- The user references something they discussed before ("last time", "remember when", "we decided to use X")
|
|
19
|
-
- The user asks about a project pattern, decision, or convention you have no inline context for
|
|
20
|
-
- You're debugging an error message that may have been seen + resolved before
|
|
21
|
-
- The auto-recall block (`<everme_recall>...</everme_recall>` in your context) is empty or clearly unrelated to the current task
|
|
22
|
-
- The user explicitly asks you to "search my memory" / "recall" / "look up"
|
|
20
|
+
The plugin's native hooks already inject `<everme_profile>` at session start and `<everme_recall>` before each prompt, and they save the conversation automatically. So:
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
22
|
+
- If this turn already carries a non-empty, relevant `<everme_recall>` block — do NOT call `mem_search` for the same topic.
|
|
23
|
+
- If the recall block is missing, empty, or clearly unrelated AND the task depends on history ("last time", "we decided", "did we fix this before", project conventions, previously solved errors) — call `mem_search` once, with a SHORT topic query, not the whole user message.
|
|
24
|
+
- Never repeat an identical query within the same turn.
|
|
25
|
+
- Call `mem_context` only when no `<everme_profile>` block was injected this session.
|
|
26
|
+
|
|
27
|
+
## Save honesty
|
|
28
|
+
|
|
29
|
+
`mem_save_fact` returns the real extraction verdict. Only `extracted: true` / `profileUpdated: true` means the profile updated — then you may tell the user the fact is remembered. On `status: "no_extraction"` the profile did NOT update: say so plainly, do not auto-retry, and do not claim success.
|
|
28
30
|
|
|
29
31
|
## Best practices
|
|
30
32
|
|