@everme/claude-code 0.6.3 → 0.6.5
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 +36 -1
- package/hooks/scripts/lib/adapter.js +71 -12
- package/hooks/scripts/lib/transcript.js +97 -15
- package/hooks/scripts/mcp-server.js +20 -9
- package/package.json +2 -2
- package/skills/memory-tools.md +10 -6
|
@@ -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.6.
|
|
13
|
+
"version": "0.6.5",
|
|
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.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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
|
@@ -7,9 +7,20 @@ Automatic memory recall + persistence for Claude Code, backed by the EverMe gate
|
|
|
7
7
|
- **SessionStart** → loads the profile snapshot from past sessions and injects it as `additionalContext` for the model + a one-line `🧠 EverMe loaded N memory items` system message for you.
|
|
8
8
|
- **UserPromptSubmit** → searches your memory for content relevant to the prompt you just typed and injects it BEFORE the model sees the prompt. Silent when no relevant hit (no nag).
|
|
9
9
|
- **Stop** → persists the just-finished raw turn (including tool calls/results) with `flush:false`; every fifth turn triggers extraction.
|
|
10
|
-
- **SubagentStop** →
|
|
10
|
+
- **SubagentStop** → excludes internal task/user wrappers, then skips an independent child transcript without a real user (`subagent_without_user`). Admitted child trajectories keep their stable conversation ID and per-transcript checkpoint.
|
|
11
11
|
- **SessionEnd** → sends a flush-only request so short sessions still extract memory without repeating messages.
|
|
12
12
|
|
|
13
|
+
Write hooks require a nonblank native string `session_id`; `SubagentStop` also requires a nonblank native string `agent_id`. Missing or invalid IDs produce a bounded diagnostic and skip the write, without generating a fallback ID. Valid child IDs retain the `session_id__agent_id` format. Read-only recall is unaffected.
|
|
14
|
+
|
|
15
|
+
Tool arguments must encode valid JSON. Invalid calls are skipped with aggregate diagnostics, together with results whose native ID identifies that call uniquely. Other text and valid calls remain unchanged; conflicting source IDs are not used to guess result ownership. Tool result text itself need not be JSON.
|
|
16
|
+
|
|
17
|
+
Tool names must be nonblank native strings. Missing or invalid names follow the same skip-and-diagnose policy, without substituting `unknown`; an actual native tool named `unknown` remains valid.
|
|
18
|
+
|
|
19
|
+
Native assistant `message.stop_reason: "end_turn"` is retained as local batching
|
|
20
|
+
metadata so complete turns take priority over tool-pair boundaries. Missing or
|
|
21
|
+
other stop reasons do not imply completion. This metadata is not sent to the
|
|
22
|
+
gateway; oversized turns still use the existing tool-pair/hard-limit fallback.
|
|
23
|
+
|
|
13
24
|
Plus:
|
|
14
25
|
|
|
15
26
|
- **MCP server** exposing the read-only `mem_search` / `mem_context` tools for explicit recall (saving is automatic via the hooks).
|
|
@@ -105,3 +116,27 @@ install.sh bash installer
|
|
|
105
116
|
## License
|
|
106
117
|
|
|
107
118
|
Apache-2.0
|
|
119
|
+
# Task-aware ingestion
|
|
120
|
+
|
|
121
|
+
Claude Code enables the shared task-aware batching policy: preserve fitting
|
|
122
|
+
user tasks, compress only byte-oversized tool results with 4/3/2 KiB budgets,
|
|
123
|
+
then split remaining overflow at text, tool-pair, or hard boundaries. Existing
|
|
124
|
+
incremental reads, checkpoint commits and flush cadence are unchanged.
|
|
125
|
+
|
|
126
|
+
SubagentStop skips child transcripts without a real user after filtering,
|
|
127
|
+
reports `subagent_without_user`, and does not advance their checkpoints. Root
|
|
128
|
+
assistant-only deltas remain eligible. No Codex-specific goal envelope is
|
|
129
|
+
assumed for Claude's native transcript format.
|
|
130
|
+
|
|
131
|
+
## Fork history and progress
|
|
132
|
+
|
|
133
|
+
Root write hooks exclude records explicitly belonging to another native
|
|
134
|
+
`sessionId` before selecting messages, resolving the turn ID, and calculating
|
|
135
|
+
checkpoint counts or `baseTurn`. The hook's `session_id` remains authoritative.
|
|
136
|
+
Records without a session ID retain the existing compatibility behavior;
|
|
137
|
+
subagent selection is unchanged. First capture still selects only the last turn.
|
|
138
|
+
|
|
139
|
+
This corrects new-state coordinates only. Existing checkpoints and server
|
|
140
|
+
watermarks are not migrated or cleared; previously contaminated progress can
|
|
141
|
+
still skip messages and requires a separate recovery decision. No historical
|
|
142
|
+
replay is triggered by this change.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readTranscript, extractAgentMessages } from "./transcript.js";
|
|
4
|
+
import { turnOrdinals } from "@everme/agent-sdk";
|
|
4
5
|
|
|
5
6
|
const CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit"]);
|
|
7
|
+
const WRITE_EVENTS = new Set(["Stop", "SubagentStop", "SessionEnd", "PreCompact"]);
|
|
6
8
|
|
|
7
9
|
export const claudeCodeAdapter = {
|
|
8
10
|
platform: "claude-code",
|
|
@@ -10,6 +12,7 @@ export const claudeCodeAdapter = {
|
|
|
10
12
|
// every one of them, so the SDK claims channel="hook" for these writes and
|
|
11
13
|
// they enter L1-2's denominator. Whole-session hosts leave this unset.
|
|
12
14
|
turnBoundary: "stop",
|
|
15
|
+
taskBatching: true,
|
|
13
16
|
|
|
14
17
|
envFile() {
|
|
15
18
|
return process.env.EVERME_ENV_FILE_PATH || path.join(os.homedir(), ".claude", "everme.env");
|
|
@@ -17,10 +20,14 @@ export const claudeCodeAdapter = {
|
|
|
17
20
|
|
|
18
21
|
normalizeInput(rawInput, event) {
|
|
19
22
|
const isSubagent = event === "SubagentStop";
|
|
20
|
-
const parentSessionId = rawInput?.session_id
|
|
21
|
-
const agentId = rawInput?.agent_id
|
|
23
|
+
const parentSessionId = nativeId(rawInput?.session_id);
|
|
24
|
+
const agentId = nativeId(rawInput?.agent_id);
|
|
25
|
+
const missingIdentity = !parentSessionId
|
|
26
|
+
? "missing_session_id"
|
|
27
|
+
: isSubagent && !agentId ? "missing_agent_id" : "";
|
|
28
|
+
if (missingIdentity && WRITE_EVENTS.has(event)) logSkippedWrite(missingIdentity);
|
|
22
29
|
return {
|
|
23
|
-
sessionId: isSubagent ? `${parentSessionId}__${agentId}` : parentSessionId,
|
|
30
|
+
sessionId: missingIdentity ? "" : isSubagent ? `${parentSessionId}__${agentId}` : parentSessionId,
|
|
24
31
|
transcriptPath: isSubagent
|
|
25
32
|
? rawInput?.agent_transcript_path || ""
|
|
26
33
|
: rawInput?.transcript_path || "",
|
|
@@ -41,7 +48,7 @@ export const claudeCodeAdapter = {
|
|
|
41
48
|
// "" (dedup disabled) when the transcript has no uuids.
|
|
42
49
|
async resolveTurnId(input) {
|
|
43
50
|
if (!input?.transcriptPath) return "";
|
|
44
|
-
const lines = await
|
|
51
|
+
const lines = await readSessionLines(input);
|
|
45
52
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
46
53
|
try {
|
|
47
54
|
const event = JSON.parse(lines[index]);
|
|
@@ -55,9 +62,11 @@ export const claudeCodeAdapter = {
|
|
|
55
62
|
|
|
56
63
|
async readLastTurn(input) {
|
|
57
64
|
if (!input?.transcriptPath) return [];
|
|
58
|
-
const messages = extractAgentMessages(await
|
|
65
|
+
const messages = extractAgentMessages(await readSessionLines(input), {
|
|
59
66
|
subagent: input.isSubagent,
|
|
67
|
+
diagnostic: (line) => console.error(line),
|
|
60
68
|
});
|
|
69
|
+
if (skipUserlessChild(input, messages)) return [];
|
|
61
70
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
62
71
|
if (messages[index]?.role === "user") return messages.slice(index);
|
|
63
72
|
}
|
|
@@ -65,24 +74,40 @@ export const claudeCodeAdapter = {
|
|
|
65
74
|
},
|
|
66
75
|
|
|
67
76
|
async readStoreBatches(input, { checkpointStore } = {}) {
|
|
77
|
+
if (!nativeId(input?.sessionId)) {
|
|
78
|
+
logSkippedWrite("missing_session_id");
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
68
81
|
if (!input?.transcriptPath) return [];
|
|
69
|
-
const messages = extractAgentMessages(await
|
|
82
|
+
const messages = extractAgentMessages(await readSessionLines(input), {
|
|
70
83
|
subagent: input.isSubagent,
|
|
84
|
+
diagnostic: (line) => console.error(line),
|
|
71
85
|
});
|
|
86
|
+
if (skipUserlessChild(input, messages)) return [];
|
|
72
87
|
const stateId = `claude-code:${input.sessionId}`;
|
|
73
88
|
const checkpoint = checkpointStore
|
|
74
89
|
? await checkpointStore.read(stateId)
|
|
75
90
|
: { initialized: false, uploadedCount: 0 };
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
delta = input.isSubagent ? messages : lastRootTurn(messages);
|
|
81
|
-
}
|
|
91
|
+
const continuation = checkpoint.initialized && checkpoint.uploadedCount <= messages.length;
|
|
92
|
+
const delta = continuation
|
|
93
|
+
? messages.slice(checkpoint.uploadedCount)
|
|
94
|
+
: (input.isSubagent ? messages : lastRootTurn(messages));
|
|
82
95
|
if (!delta.length) return [];
|
|
83
96
|
return [{
|
|
84
97
|
conversationId: input.sessionId,
|
|
85
98
|
messages: delta,
|
|
99
|
+
// Address the delta by its absolute turn only when everything before it
|
|
100
|
+
// is known to be covered: a checkpoint continuation, or the whole
|
|
101
|
+
// transcript. A cold-start tail (no checkpoint yet -- a resumed
|
|
102
|
+
// pre-existing session, a wiped state dir) must not claim the turns it
|
|
103
|
+
// skipped, or the importer would trim the history it still has to bring
|
|
104
|
+
// in; unaddressed, the gate appends it at the current watermark instead.
|
|
105
|
+
...(continuation || delta.length === messages.length
|
|
106
|
+
? { baseTurn: turnOrdinals(messages)[messages.length - delta.length] }
|
|
107
|
+
// A cold-start tail also declares no completed turn: appended at the
|
|
108
|
+
// watermark it would be recorded as turn 0 and the importer would
|
|
109
|
+
// then trim the real turn 0 as covered.
|
|
110
|
+
: { turns: 0 }),
|
|
86
111
|
checkpoint: { stateId, uploadedCount: messages.length },
|
|
87
112
|
}];
|
|
88
113
|
},
|
|
@@ -102,6 +127,40 @@ export const claudeCodeAdapter = {
|
|
|
102
127
|
},
|
|
103
128
|
};
|
|
104
129
|
|
|
130
|
+
async function readSessionLines(input) {
|
|
131
|
+
const lines = await readTranscript(input.transcriptPath);
|
|
132
|
+
if (input.isSubagent || !nativeId(input.sessionId)) return lines;
|
|
133
|
+
let excluded = 0;
|
|
134
|
+
const selected = lines.filter((line) => {
|
|
135
|
+
try {
|
|
136
|
+
const sessionId = nativeId(JSON.parse(line)?.sessionId);
|
|
137
|
+
if (sessionId && sessionId !== input.sessionId) {
|
|
138
|
+
excluded++;
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Leave malformed records to the existing parser.
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
if (excluded) console.error(`[everme] claude-code parse reason=claude_inherited_history excluded_records=${excluded}`);
|
|
147
|
+
return selected;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function nativeId(value) {
|
|
151
|
+
return typeof value === "string" && value.trim() ? value : "";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function skipUserlessChild(input, messages) {
|
|
155
|
+
if (!input.isSubagent || messages.some((message) => message.role === "user")) return false;
|
|
156
|
+
logSkippedWrite("subagent_without_user");
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function logSkippedWrite(reason) {
|
|
161
|
+
console.error(`[everme] claude-code store stage=skip reason=${reason}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
105
164
|
function lastRootTurn(messages) {
|
|
106
165
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
107
166
|
if (messages[index]?.role === "user") return messages.slice(index);
|
|
@@ -133,8 +133,9 @@ export function extractTurns(lines) {
|
|
|
133
133
|
return turns;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
export function extractAgentMessages(lines, { subagent = false } = {}) {
|
|
136
|
+
export function extractAgentMessages(lines, { subagent = false, diagnostic = () => {} } = {}) {
|
|
137
137
|
const messages = [];
|
|
138
|
+
const dropped = { calls: 0, results: 0 };
|
|
138
139
|
for (const line of lines) {
|
|
139
140
|
let ev;
|
|
140
141
|
try {
|
|
@@ -160,7 +161,7 @@ export function extractAgentMessages(lines, { subagent = false } = {}) {
|
|
|
160
161
|
// messages (with their own toolCallId), free text becomes a
|
|
161
162
|
// role=user message. A single CC user event can therefore emit
|
|
162
163
|
// multiple EverMe messages.
|
|
163
|
-
const toolResults = extractToolResults(rawContent, timestamp);
|
|
164
|
+
const toolResults = extractToolResults(rawContent, timestamp, dropped);
|
|
164
165
|
messages.push(...toolResults);
|
|
165
166
|
if (subagent) continue;
|
|
166
167
|
const text = normalizeClaudeUserText(textFromContent(rawContent));
|
|
@@ -170,20 +171,93 @@ export function extractAgentMessages(lines, { subagent = false } = {}) {
|
|
|
170
171
|
continue;
|
|
171
172
|
}
|
|
172
173
|
if (role === AGENT_MEMORY_ROLES.ASSISTANT) {
|
|
173
|
-
const msg = agentAssistantMessage(rawContent, timestamp);
|
|
174
|
-
if (msg)
|
|
174
|
+
const msg = agentAssistantMessage(rawContent, timestamp, dropped);
|
|
175
|
+
if (msg) {
|
|
176
|
+
if (inner?.stop_reason === "end_turn") msg.turnComplete = true;
|
|
177
|
+
messages.push(msg);
|
|
178
|
+
}
|
|
175
179
|
continue;
|
|
176
180
|
}
|
|
177
181
|
// Legacy flat tool-role fallback: { role:"tool", content, toolCallId }
|
|
178
182
|
if (role === AGENT_MEMORY_ROLES.TOOL || ev.type === "tool_result") {
|
|
179
|
-
const toolCallId = ev.toolCallId
|
|
180
|
-
if (!toolCallId)
|
|
183
|
+
const toolCallId = firstToolId(ev.toolCallId, ev.tool_call_id, ev.tool_use_id);
|
|
184
|
+
if (!toolCallId) {
|
|
185
|
+
dropped.results++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
181
188
|
const content =
|
|
182
189
|
typeof rawContent === "string" ? rawContent : safeJsonStringify(rawContent);
|
|
183
190
|
messages.push({ role: AGENT_MEMORY_ROLES.TOOL, timestamp, toolCallId, content });
|
|
184
191
|
}
|
|
185
192
|
}
|
|
186
|
-
|
|
193
|
+
const filtered = filterInvalidToolCalls(messages, diagnostic);
|
|
194
|
+
if (dropped.calls || dropped.results) {
|
|
195
|
+
diagnostic(`[everme] claude-code parse lines=${lines.length} messages=${filtered.length} dropped_invalid_call_id=${dropped.calls} dropped_invalid_result_id=${dropped.results}`.slice(0, 1024));
|
|
196
|
+
}
|
|
197
|
+
return filtered;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function filterInvalidToolCalls(messages, diagnostic) {
|
|
201
|
+
const callCounts = new Map();
|
|
202
|
+
const invalidCalls = new Set();
|
|
203
|
+
const invalidIds = new Map();
|
|
204
|
+
const dropped = {
|
|
205
|
+
invalid_tool_name: { calls: 0, results: 0 },
|
|
206
|
+
invalid_tool_arguments: { calls: 0, results: 0 },
|
|
207
|
+
};
|
|
208
|
+
for (const message of messages) {
|
|
209
|
+
for (const call of message.toolCalls || []) {
|
|
210
|
+
callCounts.set(call.id, (callCounts.get(call.id) || 0) + 1);
|
|
211
|
+
const reason = invalidToolCallReason(call);
|
|
212
|
+
if (reason) {
|
|
213
|
+
invalidCalls.add(call);
|
|
214
|
+
invalidIds.set(call.id, reason);
|
|
215
|
+
dropped[reason].calls++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!invalidCalls.size) return messages;
|
|
220
|
+
const filtered = [];
|
|
221
|
+
for (const message of messages) {
|
|
222
|
+
if (message.role === AGENT_MEMORY_ROLES.TOOL
|
|
223
|
+
&& invalidIds.has(message.toolCallId) && callCounts.get(message.toolCallId) === 1) {
|
|
224
|
+
dropped[invalidIds.get(message.toolCallId)].results++;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (message.toolCalls?.some(call => invalidCalls.has(call))) {
|
|
228
|
+
const kept = { ...message, toolCalls: message.toolCalls.filter(call => !invalidCalls.has(call)) };
|
|
229
|
+
if (!kept.toolCalls.length) {
|
|
230
|
+
delete kept.toolCalls;
|
|
231
|
+
if (!kept.content?.trim()) continue;
|
|
232
|
+
}
|
|
233
|
+
filtered.push(kept);
|
|
234
|
+
} else {
|
|
235
|
+
filtered.push(message);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const [reason, counts] of Object.entries(dropped)) {
|
|
239
|
+
if (counts.calls) diagnostic(`[everme] claude-code parse reason=${reason} dropped_calls=${counts.calls} dropped_results=${counts.results}`.slice(0, 1024));
|
|
240
|
+
}
|
|
241
|
+
return filtered;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function invalidToolCallReason(call) {
|
|
245
|
+
if (typeof call.name !== "string" || !call.name.trim()) return "invalid_tool_name";
|
|
246
|
+
return isJsonArguments(call.arguments) ? "" : "invalid_tool_arguments";
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function isJsonArguments(value) {
|
|
250
|
+
if (typeof value !== "string") return false;
|
|
251
|
+
try {
|
|
252
|
+
JSON.parse(value);
|
|
253
|
+
return true;
|
|
254
|
+
} catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function firstToolId(...values) {
|
|
260
|
+
return values.find(value => typeof value === "string" && value.trim().length > 0) || "";
|
|
187
261
|
}
|
|
188
262
|
|
|
189
263
|
function isInternalEvent(event, subagent) {
|
|
@@ -264,14 +338,17 @@ function envelopeValue(text, tag) {
|
|
|
264
338
|
// message. CC encodes tool_result as a content block inside a user
|
|
265
339
|
// envelope (not a separate top-level event), so without this step the
|
|
266
340
|
// entire tool round-trip is lost.
|
|
267
|
-
function extractToolResults(content, timestamp) {
|
|
341
|
+
function extractToolResults(content, timestamp, dropped) {
|
|
268
342
|
if (!Array.isArray(content)) return [];
|
|
269
343
|
const out = [];
|
|
270
344
|
for (const b of content) {
|
|
271
345
|
if (!b || typeof b !== "object") continue;
|
|
272
346
|
if (b.type !== "tool_result") continue;
|
|
273
|
-
const toolCallId = b.tool_use_id
|
|
274
|
-
if (!toolCallId)
|
|
347
|
+
const toolCallId = firstToolId(b.tool_use_id, b.toolCallId, b.tool_call_id);
|
|
348
|
+
if (!toolCallId) {
|
|
349
|
+
dropped.results++;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
275
352
|
let text;
|
|
276
353
|
if (typeof b.content === "string") {
|
|
277
354
|
text = b.content;
|
|
@@ -294,23 +371,28 @@ function extractToolResults(content, timestamp) {
|
|
|
294
371
|
return out;
|
|
295
372
|
}
|
|
296
373
|
|
|
297
|
-
function agentAssistantMessage(content, timestamp) {
|
|
374
|
+
function agentAssistantMessage(content, timestamp, dropped) {
|
|
298
375
|
if (typeof content === "string") {
|
|
299
376
|
return content ? { role: AGENT_MEMORY_ROLES.ASSISTANT, timestamp, content } : null;
|
|
300
377
|
}
|
|
301
378
|
if (!Array.isArray(content)) return null;
|
|
302
379
|
const textParts = [];
|
|
303
380
|
const toolCalls = [];
|
|
304
|
-
for (const
|
|
381
|
+
for (const b of content) {
|
|
305
382
|
if (!b || typeof b !== "object") continue;
|
|
306
383
|
if (b.type === "text" && typeof b.text === "string") {
|
|
307
384
|
textParts.push(b.text);
|
|
308
385
|
} else if (b.type === "tool_use" || b.type === "toolCall") {
|
|
386
|
+
const id = firstToolId(b.id, b.tool_use_id);
|
|
387
|
+
if (!id) {
|
|
388
|
+
dropped.calls++;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
309
391
|
const args = b.input ?? b.arguments ?? {};
|
|
310
392
|
toolCalls.push({
|
|
311
|
-
id
|
|
393
|
+
id,
|
|
312
394
|
type: AGENT_MEMORY_TOOL_CALL_TYPES.FUNCTION,
|
|
313
|
-
name: b.name
|
|
395
|
+
name: b.name,
|
|
314
396
|
arguments: typeof args === "string" ? args : safeJsonStringify(args),
|
|
315
397
|
});
|
|
316
398
|
}
|
|
@@ -319,7 +401,7 @@ function agentAssistantMessage(content, timestamp) {
|
|
|
319
401
|
const text = textParts.join("\n\n");
|
|
320
402
|
if (text) out.content = text;
|
|
321
403
|
if (toolCalls.length) out.toolCalls = toolCalls;
|
|
322
|
-
return
|
|
404
|
+
return text.trim() || toolCalls.length ? out : null;
|
|
323
405
|
}
|
|
324
406
|
|
|
325
407
|
function textFromContent(content) {
|
|
@@ -67,16 +67,27 @@ function getClient() {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// Instructions returned on initialize — Claude Code splices them into the
|
|
70
|
-
// system prompt.
|
|
71
|
-
//
|
|
72
|
-
//
|
|
70
|
+
// system prompt. The plugin's hooks always run on this host, so unlike the
|
|
71
|
+
// generic @everme/memory-mcp copy this one can state plainly that memory is
|
|
72
|
+
// kept on its own; the two READ tools cover what the injected
|
|
73
|
+
// <everme_profile> / <everme_recall> blocks do not.
|
|
73
74
|
const INSTRUCTIONS = [
|
|
74
|
-
"EverMe
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
75
|
+
"EverMe is the user's long-term memory across sessions. On this host it",
|
|
76
|
+
"listens to the conversation itself: what matters — preferences, decisions,",
|
|
77
|
+
"the work you do together — is kept without anyone saving it by hand, and",
|
|
78
|
+
"comes back into your context next time through the <everme_profile> and",
|
|
79
|
+
"<everme_recall> blocks.",
|
|
80
|
+
"",
|
|
81
|
+
"So when the user asks you to remember something, you already can. A short",
|
|
82
|
+
"acknowledgement is enough (\"Noted — I'll remember that.\"), then move on;",
|
|
83
|
+
"there is nothing to call and nothing to set up. If they ask how it works,",
|
|
84
|
+
"\"EverMe keeps a memory of our conversations\" is the whole story from their",
|
|
85
|
+
"side — the mechanics are not something to narrate, and there is nothing to",
|
|
86
|
+
"work around.",
|
|
87
|
+
"",
|
|
88
|
+
"Two read tools fill the gaps the injected blocks leave. Use them on your",
|
|
89
|
+
"own judgment rather than waiting to be asked to \"recall\":",
|
|
90
|
+
"1. The <everme_recall> block is missing, empty, or clearly off-topic AND the user leans on earlier conversations, decisions, conventions, or previously solved problems — call `mem_search` with a SHORT query, and never repeat an identical query in one turn.",
|
|
80
91
|
"2. No <everme_profile> block was injected this session — call `mem_context` once. It returns the durable Profile ONLY (no search, no episodes).",
|
|
81
92
|
].join("\n");
|
|
82
93
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everme/claude-code",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"README.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@everme/agent-sdk": "^0.6.
|
|
24
|
+
"@everme/agent-sdk": "^0.6.5"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"evermind",
|
package/skills/memory-tools.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
---
|
|
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
|
|
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. Do not repeat a search when a non-empty <everme_recall> block already exists.
|
|
3
3
|
alwaysInclude: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# EverMe Memory
|
|
6
|
+
# EverMe Memory
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
EverMe is the user's long-term memory across Claude Code sessions. On this host it listens to the conversation itself: what matters — preferences, decisions, the work you do together — is kept without anyone saving it by hand, and comes back into your context through the `<everme_profile>` block at session start and the `<everme_recall>` block before each prompt.
|
|
9
|
+
|
|
10
|
+
## When the user asks you to remember something
|
|
11
|
+
|
|
12
|
+
You already can. A short acknowledgement is enough ("Noted — I'll remember that."), then carry on with the task. There is nothing to call and nothing to set up. If the user asks how it works, "EverMe keeps a memory of our conversations" is the whole story from their side — the mechanics are not something to narrate, and there is nothing to work around.
|
|
13
|
+
|
|
14
|
+
## Two read tools
|
|
9
15
|
|
|
10
16
|
- `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.
|
|
11
17
|
- `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
18
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
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:
|
|
19
|
+
## When to call them (the injected blocks come first)
|
|
16
20
|
|
|
17
21
|
- If this turn already carries a non-empty, relevant `<everme_recall>` block — do NOT call `mem_search` for the same topic.
|
|
18
22
|
- 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.
|