@everme/claude-code 0.6.4 → 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.
|
@@ -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.
|
|
@@ -4,6 +4,7 @@ import { readTranscript, extractAgentMessages } from "./transcript.js";
|
|
|
4
4
|
import { turnOrdinals } from "@everme/agent-sdk";
|
|
5
5
|
|
|
6
6
|
const CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit"]);
|
|
7
|
+
const WRITE_EVENTS = new Set(["Stop", "SubagentStop", "SessionEnd", "PreCompact"]);
|
|
7
8
|
|
|
8
9
|
export const claudeCodeAdapter = {
|
|
9
10
|
platform: "claude-code",
|
|
@@ -11,6 +12,7 @@ export const claudeCodeAdapter = {
|
|
|
11
12
|
// every one of them, so the SDK claims channel="hook" for these writes and
|
|
12
13
|
// they enter L1-2's denominator. Whole-session hosts leave this unset.
|
|
13
14
|
turnBoundary: "stop",
|
|
15
|
+
taskBatching: true,
|
|
14
16
|
|
|
15
17
|
envFile() {
|
|
16
18
|
return process.env.EVERME_ENV_FILE_PATH || path.join(os.homedir(), ".claude", "everme.env");
|
|
@@ -18,10 +20,14 @@ export const claudeCodeAdapter = {
|
|
|
18
20
|
|
|
19
21
|
normalizeInput(rawInput, event) {
|
|
20
22
|
const isSubagent = event === "SubagentStop";
|
|
21
|
-
const parentSessionId = rawInput?.session_id
|
|
22
|
-
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);
|
|
23
29
|
return {
|
|
24
|
-
sessionId: isSubagent ? `${parentSessionId}__${agentId}` : parentSessionId,
|
|
30
|
+
sessionId: missingIdentity ? "" : isSubagent ? `${parentSessionId}__${agentId}` : parentSessionId,
|
|
25
31
|
transcriptPath: isSubagent
|
|
26
32
|
? rawInput?.agent_transcript_path || ""
|
|
27
33
|
: rawInput?.transcript_path || "",
|
|
@@ -42,7 +48,7 @@ export const claudeCodeAdapter = {
|
|
|
42
48
|
// "" (dedup disabled) when the transcript has no uuids.
|
|
43
49
|
async resolveTurnId(input) {
|
|
44
50
|
if (!input?.transcriptPath) return "";
|
|
45
|
-
const lines = await
|
|
51
|
+
const lines = await readSessionLines(input);
|
|
46
52
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
47
53
|
try {
|
|
48
54
|
const event = JSON.parse(lines[index]);
|
|
@@ -56,9 +62,11 @@ export const claudeCodeAdapter = {
|
|
|
56
62
|
|
|
57
63
|
async readLastTurn(input) {
|
|
58
64
|
if (!input?.transcriptPath) return [];
|
|
59
|
-
const messages = extractAgentMessages(await
|
|
65
|
+
const messages = extractAgentMessages(await readSessionLines(input), {
|
|
60
66
|
subagent: input.isSubagent,
|
|
67
|
+
diagnostic: (line) => console.error(line),
|
|
61
68
|
});
|
|
69
|
+
if (skipUserlessChild(input, messages)) return [];
|
|
62
70
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
63
71
|
if (messages[index]?.role === "user") return messages.slice(index);
|
|
64
72
|
}
|
|
@@ -66,10 +74,16 @@ export const claudeCodeAdapter = {
|
|
|
66
74
|
},
|
|
67
75
|
|
|
68
76
|
async readStoreBatches(input, { checkpointStore } = {}) {
|
|
77
|
+
if (!nativeId(input?.sessionId)) {
|
|
78
|
+
logSkippedWrite("missing_session_id");
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
69
81
|
if (!input?.transcriptPath) return [];
|
|
70
|
-
const messages = extractAgentMessages(await
|
|
82
|
+
const messages = extractAgentMessages(await readSessionLines(input), {
|
|
71
83
|
subagent: input.isSubagent,
|
|
84
|
+
diagnostic: (line) => console.error(line),
|
|
72
85
|
});
|
|
86
|
+
if (skipUserlessChild(input, messages)) return [];
|
|
73
87
|
const stateId = `claude-code:${input.sessionId}`;
|
|
74
88
|
const checkpoint = checkpointStore
|
|
75
89
|
? await checkpointStore.read(stateId)
|
|
@@ -113,6 +127,40 @@ export const claudeCodeAdapter = {
|
|
|
113
127
|
},
|
|
114
128
|
};
|
|
115
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
|
+
|
|
116
164
|
function lastRootTurn(messages) {
|
|
117
165
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
118
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) {
|
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",
|