@everme/claude-code 0.6.1 → 0.6.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.
@@ -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.1",
13
+ "version": "0.6.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.6.1",
3
+ "version": "0.6.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
@@ -7,11 +7,12 @@ 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** → persists the child transcript's assistant/tool trajectory under a stable child conversation id; internal task/user wrappers are excluded and a per-transcript checkpoint prevents repeats.
10
11
  - **SessionEnd** → sends a flush-only request so short sessions still extract memory without repeating messages.
11
12
 
12
13
  Plus:
13
14
 
14
- - **MCP server** exposing the canonical `mem_search` / `mem_context` / `mem_save_fact` / `mem_save_turn` tools for explicit recall and saves.
15
+ - **MCP server** exposing the read-only `mem_search` / `mem_context` tools for explicit recall (saving is automatic via the hooks).
15
16
  - **Slash commands** `/recall <q>` and `/everme-help`.
16
17
  - **Skill** (`memory-tools`) that tells Claude when/how to use the search tool.
17
18
 
@@ -21,7 +22,7 @@ Plus:
21
22
  |---|---|---|
22
23
  | Format | Generic MCP server | Claude Code plugin (hooks + commands + skill + MCP) |
23
24
  | Recall trigger | User must call MCP tool manually | Automatic on every UserPromptSubmit |
24
- | Save trigger | Explicit MCP tool call | Every Stop add + every fifth turn / SessionEnd extraction flush |
25
+ | Save trigger | Explicit MCP tool call | Stop + SubagentStop adds; every fifth root turn / SessionEnd extraction flush |
25
26
  | Auth | `EVERME_AGENT_TOKEN` (evt) | Recall supports `EVERME_API_KEY` or `EVERME_AGENT_TOKEN`; realtime writes require `EVERME_AGENT_TOKEN` + `EVERME_AGENT_ID` |
26
27
  | Backend | EverMe gateway (`/api/v1/mem/*`) | Same — runtime writes use `/mem/agent-memory`, not `/mem/sources` |
27
28
 
@@ -84,9 +85,10 @@ In a new Claude Code session:
84
85
  .claude-plugin/plugin.json plugin metadata
85
86
  .claude-plugin/marketplace.json marketplace listing
86
87
  .mcp.json MCP server registration
87
- hooks/hooks.json SessionStart / UserPromptSubmit / Stop / SessionEnd wiring
88
+ hooks/hooks.json SessionStart / UserPromptSubmit / Stop / SubagentStop / SessionEnd wiring
88
89
  hooks/scripts/inject-memories.js UserPromptSubmit handler
89
90
  hooks/scripts/store-memories.js Stop handler
91
+ hooks/scripts/store-subagent-memories.js SubagentStop handler
90
92
  hooks/scripts/session-start.js SessionStart handler
91
93
  hooks/scripts/session-summary.js SessionEnd handler
92
94
  hooks/scripts/mcp-server.js MCP server (canonical mem_* tools)
@@ -26,6 +26,4 @@ Slash:
26
26
  MCP tools:
27
27
  mem_search — hybrid search across all memory buckets
28
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
31
29
  ```
package/hooks/hooks.json CHANGED
@@ -36,6 +36,18 @@
36
36
  ]
37
37
  }
38
38
  ],
39
+ "SubagentStop": [
40
+ {
41
+ "matcher": "*",
42
+ "hooks": [
43
+ {
44
+ "type": "command",
45
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/store-subagent-memories.js",
46
+ "timeout": 30
47
+ }
48
+ ]
49
+ }
50
+ ],
39
51
  "SessionEnd": [
40
52
  {
41
53
  "matcher": "*",
@@ -11,14 +11,20 @@ export const claudeCodeAdapter = {
11
11
  return process.env.EVERME_ENV_FILE_PATH || path.join(os.homedir(), ".claude", "everme.env");
12
12
  },
13
13
 
14
- normalizeInput(rawInput) {
14
+ normalizeInput(rawInput, event) {
15
+ const isSubagent = event === "SubagentStop";
16
+ const parentSessionId = rawInput?.session_id || "claude-code-session";
17
+ const agentId = rawInput?.agent_id || "subagent";
15
18
  return {
16
- sessionId: rawInput?.session_id || "claude-code-session",
17
- transcriptPath: rawInput?.transcript_path || "",
19
+ sessionId: isSubagent ? `${parentSessionId}__${agentId}` : parentSessionId,
20
+ transcriptPath: isSubagent
21
+ ? rawInput?.agent_transcript_path || ""
22
+ : rawInput?.transcript_path || "",
18
23
  cwd: rawInput?.cwd || "",
19
24
  prompt: rawInput?.prompt || "",
20
25
  turnId: rawInput?.turn_id || "",
21
26
  source: rawInput?.source || "",
27
+ isSubagent,
22
28
  };
23
29
  },
24
30
 
@@ -45,13 +51,38 @@ export const claudeCodeAdapter = {
45
51
 
46
52
  async readLastTurn(input) {
47
53
  if (!input?.transcriptPath) return [];
48
- const messages = extractAgentMessages(await readTranscript(input.transcriptPath));
54
+ const messages = extractAgentMessages(await readTranscript(input.transcriptPath), {
55
+ subagent: input.isSubagent,
56
+ });
49
57
  for (let index = messages.length - 1; index >= 0; index -= 1) {
50
58
  if (messages[index]?.role === "user") return messages.slice(index);
51
59
  }
52
60
  return messages;
53
61
  },
54
62
 
63
+ async readStoreBatches(input, { checkpointStore } = {}) {
64
+ if (!input?.transcriptPath) return [];
65
+ const messages = extractAgentMessages(await readTranscript(input.transcriptPath), {
66
+ subagent: input.isSubagent,
67
+ });
68
+ const stateId = `claude-code:${input.sessionId}`;
69
+ const checkpoint = checkpointStore
70
+ ? await checkpointStore.read(stateId)
71
+ : { initialized: false, uploadedCount: 0 };
72
+ let delta;
73
+ if (checkpoint.initialized && checkpoint.uploadedCount <= messages.length) {
74
+ delta = messages.slice(checkpoint.uploadedCount);
75
+ } else {
76
+ delta = input.isSubagent ? messages : lastRootTurn(messages);
77
+ }
78
+ if (!delta.length) return [];
79
+ return [{
80
+ conversationId: input.sessionId,
81
+ messages: delta,
82
+ checkpoint: { stateId, uploadedCount: messages.length },
83
+ }];
84
+ },
85
+
55
86
  formatOutput(event, { block = "", count = 0 } = {}) {
56
87
  if (!CONTEXT_EVENTS.has(event) || !block) return {};
57
88
  const systemMessage = event === "SessionStart"
@@ -66,3 +97,10 @@ export const claudeCodeAdapter = {
66
97
  };
67
98
  },
68
99
  };
100
+
101
+ function lastRootTurn(messages) {
102
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
103
+ if (messages[index]?.role === "user") return messages.slice(index);
104
+ }
105
+ return messages;
106
+ }
@@ -133,7 +133,7 @@ export function extractTurns(lines) {
133
133
  return turns;
134
134
  }
135
135
 
136
- export function extractAgentMessages(lines) {
136
+ export function extractAgentMessages(lines, { subagent = false } = {}) {
137
137
  const messages = [];
138
138
  for (const line of lines) {
139
139
  let ev;
@@ -142,6 +142,7 @@ export function extractAgentMessages(lines) {
142
142
  } catch {
143
143
  continue;
144
144
  }
145
+ if (isInternalEvent(ev, subagent)) continue;
145
146
  const timestamp = normalizeTimestamp(ev.timestamp);
146
147
  // Real Claude Code transcript schema is nested: each line is
147
148
  // { "type":"user"|"assistant", "message":{role, content}, ... }
@@ -161,8 +162,11 @@ export function extractAgentMessages(lines) {
161
162
  // multiple EverMe messages.
162
163
  const toolResults = extractToolResults(rawContent, timestamp);
163
164
  messages.push(...toolResults);
164
- const text = textFromContent(rawContent);
165
- if (text) messages.push({ role: AGENT_MEMORY_ROLES.USER, timestamp, content: text });
165
+ if (subagent) continue;
166
+ const text = normalizeClaudeUserText(textFromContent(rawContent));
167
+ if (text) {
168
+ messages.push({ role: AGENT_MEMORY_ROLES.USER, timestamp, content: text });
169
+ }
166
170
  continue;
167
171
  }
168
172
  if (role === AGENT_MEMORY_ROLES.ASSISTANT) {
@@ -182,6 +186,79 @@ export function extractAgentMessages(lines) {
182
186
  return messages;
183
187
  }
184
188
 
189
+ function isInternalEvent(event, subagent) {
190
+ if (event?.isMeta === true) return true;
191
+ if (subagent) return false;
192
+ return event?.isSidechain === true
193
+ || (typeof event?.agentId === "string" && event.agentId.length > 0);
194
+ }
195
+
196
+ function normalizeClaudeUserText(text) {
197
+ let trimmed = String(text || "").trim();
198
+ const initialCommand = commandIntent(trimmed);
199
+ if (initialCommand) return initialCommand;
200
+ if (hasEnvelopePrefix(trimmed, "command-message")) return "";
201
+
202
+ const injectedTags = [
203
+ "system-reminder",
204
+ "preview-annotation-context",
205
+ "task-notification",
206
+ "local-command-caveat",
207
+ "local-command-stdout",
208
+ "bash-input",
209
+ "bash-stdout",
210
+ "command-name",
211
+ "turn_aborted",
212
+ ];
213
+ for (;;) {
214
+ let stripped = false;
215
+ for (const tag of injectedTags) {
216
+ const remaining = stripLeadingEnvelope(trimmed, tag);
217
+ if (remaining !== null) {
218
+ trimmed = remaining;
219
+ stripped = true;
220
+ break;
221
+ }
222
+ }
223
+ if (!stripped) break;
224
+ }
225
+ if (!trimmed) return "";
226
+ const command = commandIntent(trimmed);
227
+ if (command) return command;
228
+ return hasEnvelopePrefix(trimmed, "command-message") ? "" : trimmed;
229
+ }
230
+
231
+ function commandIntent(text) {
232
+ if (!hasEnvelopePrefix(text, "command-message")) return "";
233
+ const name = envelopeValue(text, "command-name");
234
+ if (!name.startsWith("/")) return "";
235
+ const args = envelopeValue(text, "command-args");
236
+ return `${name} ${args}`.trim();
237
+ }
238
+
239
+ function stripLeadingEnvelope(text, tag) {
240
+ if (!hasEnvelopePrefix(text, tag)) return null;
241
+ const close = `</${tag}>`;
242
+ const end = text.indexOf(close);
243
+ if (end < 0) return null;
244
+ return text.slice(end + close.length).trim();
245
+ }
246
+
247
+ function hasEnvelopePrefix(text, tag) {
248
+ if (!text.startsWith(`<${tag}`)) return false;
249
+ return [">", " ", "\t", "\n", "\r"].includes(text.at(tag.length + 1));
250
+ }
251
+
252
+ function envelopeValue(text, tag) {
253
+ const open = `<${tag}>`;
254
+ const close = `</${tag}>`;
255
+ const start = text.indexOf(open);
256
+ if (start < 0) return "";
257
+ const valueStart = start + open.length;
258
+ const end = text.indexOf(close, valueStart);
259
+ return end < 0 ? "" : text.slice(valueStart, end).trim();
260
+ }
261
+
185
262
  // extractToolResults pulls every `tool_result` block out of a CC user-
186
263
  // envelope content array and converts each into an EverMe role=tool
187
264
  // message. CC encodes tool_result as a content block inside a user
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /**
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).
3
+ * MCP server bundled with the Claude Code plugin. Exposes the read-only
4
+ * EverMe tools (mem_search / mem_context the same public ABI as
5
+ * @everme/memory-mcp and the Go hosted /mcp surface). Saving is automatic
6
+ * via the plugin's hooks; the MCP write tools were retired.
7
7
  *
8
8
  * Wire format: MCP stdio transport (JSON-RPC 2.0 framed by line).
9
9
  * We hand-roll the tiny subset Claude Code uses rather than pulling
@@ -21,9 +21,6 @@ import {
21
21
  createClient,
22
22
  getContext,
23
23
  searchMemory,
24
- saveAgentMemory,
25
- savePersonalMemory,
26
- AGENT_MEMORY_ROLES,
27
24
  redactError,
28
25
  describeError,
29
26
  EvermeError,
@@ -76,13 +73,11 @@ function getClient() {
76
73
  const INSTRUCTIONS = [
77
74
  "EverMe memory is connected. This plugin's native hooks already inject a",
78
75
  "<everme_profile> block at session start and a <everme_recall> block before",
79
- "each prompt, and they save the conversation automatically — so the tools",
80
- "below are for the cases the hooks do not cover. Call them AUTONOMOUSLY when",
81
- "a trigger fires; never wait for the user to say \"remember\" or \"recall\".",
76
+ "each prompt, and they save the conversation automatically — so saving needs",
77
+ "no tool call. These two READ tools are for the cases the hooks do not cover;",
78
+ "call them AUTONOMOUSLY when a trigger fires, never waiting to be asked.",
82
79
  "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.",
83
- "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.",
84
- "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.",
85
- "4. No <everme_profile> block was injected this session — call `mem_context` once. It returns the durable Profile ONLY (no search, no episodes).",
80
+ "2. No <everme_profile> block was injected this session — call `mem_context` once. It returns the durable Profile ONLY (no search, no episodes).",
86
81
  ].join("\n");
87
82
 
88
83
  const TOOLS = [
@@ -152,149 +147,6 @@ const TOOLS = [
152
147
  },
153
148
  },
154
149
  },
155
- {
156
- name: "mem_save_turn",
157
- description:
158
- "Persist a conversation trajectory in realtime via /mem/agent-memory. " +
159
- "Use sessionKey as conversationId.\n\n" +
160
- "Call this when a task was solved in a way worth reusing AND the " +
161
- "trajectory is not already captured by the plugin's automatic " +
162
- "transcript save. Pass the COMPLETE round-trip — messages: [{role, " +
163
- "content, timestamp?, toolCalls?, toolCallId?}] — EverOS only " +
164
- "extracts agent_case / agent_skill from trajectories carrying the " +
165
- "full tool round-trip.\n\n" +
166
- "By default flush=true: extraction into episodic / agent_case / " +
167
- "agent_skill runs right away. Pass flush=false for append-only " +
168
- "accumulation. The primary trajectory path extracts episodic / case / " +
169
- "skill memory; chat-dual-write backends may also update the user's " +
170
- "Profile. Check profileUpdated for the derived profile verdict, and " +
171
- "use mem_save_fact for deliberate durable user facts.",
172
- inputSchema: {
173
- type: "object",
174
- properties: {
175
- role: {
176
- type: "string",
177
- enum: ["user", "assistant", "tool"],
178
- description: "Role for the single-message form. Ignored when messages[] is set.",
179
- },
180
- text: { type: "string", description: "Content for the single-message form. Ignored when messages[] is set." },
181
- timestamp: { type: "integer", description: "Unix milliseconds; defaults to now. Single-message form only." },
182
- toolCallId: { type: "string", description: "Required when role=tool. Single-message form only." },
183
- toolCalls: {
184
- type: "array",
185
- description:
186
- "Tool invocations made by an assistant message. Required when " +
187
- "the assistant called tools — without this the tool round-trip " +
188
- "can never be reconstructed downstream and EverOS will not " +
189
- "produce agent_case / agent_skill from the turn.",
190
- items: {
191
- type: "object",
192
- properties: {
193
- id: { type: "string" },
194
- name: { type: "string" },
195
- arguments: { type: "string", description: "JSON-encoded tool arguments (stringified, not an object)." },
196
- },
197
- required: ["id", "name", "arguments"],
198
- },
199
- },
200
- messages: {
201
- type: "array",
202
- description:
203
- "Multi-message trajectory. Preferred for recording a complete " +
204
- "user → assistant{tool_use} → tool{tool_result} → assistant cycle.",
205
- items: {
206
- type: "object",
207
- properties: {
208
- role: { type: "string", enum: ["user", "assistant", "tool"] },
209
- content: { description: "String text, or array of content items (text / image / doc)." },
210
- timestamp: { type: "integer" },
211
- toolCallId: { type: "string", description: "Required when role=tool." },
212
- toolCalls: {
213
- type: "array",
214
- items: {
215
- type: "object",
216
- properties: {
217
- id: { type: "string" },
218
- name: { type: "string" },
219
- arguments: { type: "string" },
220
- },
221
- required: ["id", "name", "arguments"],
222
- },
223
- },
224
- },
225
- required: ["role"],
226
- },
227
- },
228
- sessionKey: { type: "string", description: "Session id; defaults to 'default'." },
229
- flush: {
230
- type: "boolean",
231
- default: true,
232
- description:
233
- "true (default) = trigger EverOS extraction into " +
234
- "episodic_memory / agent_case / agent_skill after writing; " +
235
- "false = append-only, messages are searchable as raw_messages " +
236
- "only and extraction is deferred until a later flush. The " +
237
- "default flipped from false to true because callers rarely " +
238
- "issued an explicit follow-up flush, leaving trajectories " +
239
- "permanently stuck as raw_messages with zero case/skill.",
240
- },
241
- },
242
- },
243
- },
244
- {
245
- name: "mem_save_fact",
246
- description:
247
- "Persist a durable fact about the USER (a preference, habit, trait, " +
248
- "or decision) via the long-term PROFILE write path — the block loaded " +
249
- "at the start of every session.\n\n" +
250
- "Call this proactively, without being asked, the moment the user " +
251
- "states something true about themselves that should outlive this " +
252
- "conversation (\"I love summer\", \"sign my docs as Alice\").\n\n" +
253
- "This is the direct profile-producing sibling of mem_save_turn — " +
254
- "mem_save_turn primarily records trajectories, though chat-dual-write " +
255
- "backends may derive a profile update. With flush=true (default) " +
256
- "the call runs the synchronous materialise path and returns the real " +
257
- "EverOS verdict: only `extracted:true` / `profileUpdated:true` " +
258
- "confirms the fact reached the profile. `status:\"no_extraction\"` " +
259
- "means the profile did NOT update — never claim the fact was " +
260
- "remembered in that case, and do not auto-retry.",
261
- inputSchema: {
262
- type: "object",
263
- properties: {
264
- fact: {
265
- type: "string",
266
- description:
267
- "A single user-stated fact, recorded as one user-role message. " +
268
- "Ignored when messages[] is set.",
269
- },
270
- messages: {
271
- type: "array",
272
- description:
273
- "Explicit user/assistant turns. Tool roles are not accepted on " +
274
- "this path. Preferred when you want to capture both the user's " +
275
- "statement and your acknowledgement.",
276
- items: {
277
- type: "object",
278
- properties: {
279
- role: { type: "string", enum: ["user", "assistant"] },
280
- content: { description: "String text, or array of content items." },
281
- timestamp: { type: "integer", description: "Unix milliseconds; defaults to now." },
282
- },
283
- required: ["role"],
284
- },
285
- },
286
- sessionKey: { type: "string", description: "Session id; defaults to 'default'." },
287
- flush: {
288
- type: "boolean",
289
- default: true,
290
- description:
291
- "true (default) = issue EverOS flush and return its verdict; " +
292
- "false = skip extraction entirely (fact accepted but not in the " +
293
- "profile block).",
294
- },
295
- },
296
- },
297
- },
298
150
  ];
299
151
 
300
152
  const handlers = {
@@ -341,78 +193,6 @@ const handlers = {
341
193
  const text = ctx?.context || "_(no profile available — your EverMe account has no extracted memories yet)_";
342
194
  return ok(appendRequestID(redactError(text), ctx?.requestId));
343
195
  }
344
- case "mem_save_turn": {
345
- let messages;
346
- if (Array.isArray(args.messages) && args.messages.length) {
347
- messages = args.messages.map(normaliseTurnMessage);
348
- } else {
349
- messages = [normaliseTurnMessage({
350
- role: args.role || AGENT_MEMORY_ROLES.USER,
351
- content: args.text,
352
- timestamp: args.timestamp,
353
- toolCallId: args.toolCallId,
354
- toolCalls: args.toolCalls,
355
- })];
356
- }
357
- const res = await saveAgentMemory(getClient(), {
358
- conversationId: args.sessionKey || "default",
359
- messages,
360
- flush: args.flush !== false,
361
- }, stderrLog);
362
- return okJson({
363
- saved: !!res,
364
- accepted: !!res,
365
- status: res?.status || null,
366
- messageCount: res?.messageCount || 0,
367
- flushed: !!res?.flushed,
368
- profileStatus: res?.personalStatus || null,
369
- profileUpdated: !!res?.personalExtracted,
370
- requestId: res?.requestId || null,
371
- });
372
- }
373
- case "mem_save_fact": {
374
- let messages;
375
- if (Array.isArray(args.messages) && args.messages.length) {
376
- const bad = args.messages.find(
377
- (m) => m?.role !== AGENT_MEMORY_ROLES.USER && m?.role !== AGENT_MEMORY_ROLES.ASSISTANT,
378
- );
379
- if (bad) {
380
- return errResp(
381
- `mem_save_fact accepts only 'user' or 'assistant' roles; got ${JSON.stringify(bad?.role)}. ` +
382
- "The personal-memory path does not record tool turns — use mem_save_turn for trajectories.",
383
- );
384
- }
385
- messages = args.messages.map((m) => ({
386
- role: m?.role,
387
- content: m?.content !== undefined ? m.content : m?.text,
388
- timestamp: Number(m?.timestamp) || Date.now(),
389
- }));
390
- } else if (typeof args.fact === "string" && args.fact.trim()) {
391
- messages = [{ role: AGENT_MEMORY_ROLES.USER, content: args.fact, timestamp: Date.now() }];
392
- } else {
393
- return errResp("mem_save_fact requires either `fact` or a non-empty `messages` array");
394
- }
395
- const res = await savePersonalMemory(getClient(), {
396
- conversationId: args.sessionKey || "default",
397
- messages,
398
- flush: args.flush !== false,
399
- }, stderrLog);
400
- if (!res) {
401
- return errResp("mem_save_fact wrote nothing — every message had empty content after normalization");
402
- }
403
- return okJson({
404
- saved: true,
405
- accepted: true,
406
- status: res?.status || null,
407
- messageCount: res?.messageCount || 0,
408
- flushed: !!res?.flushed,
409
- extracted: !!res?.extracted,
410
- // profileUpdated aliases extracted — the only signal that the
411
- // fact really materialised into the profile.
412
- profileUpdated: !!res?.extracted,
413
- requestId: res?.requestId || null,
414
- });
415
- }
416
196
  default:
417
197
  return errResp(`unknown tool: ${name}`);
418
198
  }
@@ -440,22 +220,6 @@ function appendRequestID(text, requestId) {
440
220
  return `${text}\n\n_(requestId: ${requestId})_`;
441
221
  }
442
222
 
443
- // normaliseTurnMessage coerces an LLM-provided message into the SDK
444
- // agent-memory shape — accepts both legacy {role, text} and canonical
445
- // {role, content, toolCalls, toolCallId} forms. Mirrors the equivalent
446
- // helper in @everme/memory-mcp (kept local: host packages must not
447
- // import each other).
448
- function normaliseTurnMessage(m) {
449
- const role = m?.role || AGENT_MEMORY_ROLES.USER;
450
- const out = { role, timestamp: Number(m?.timestamp) || Date.now() };
451
- if (m?.content !== undefined) out.content = m.content;
452
- else if (m?.text !== undefined) out.content = String(m.text);
453
- if (Array.isArray(m?.toolCalls) && m.toolCalls.length) out.toolCalls = m.toolCalls;
454
- if (role === AGENT_MEMORY_ROLES.TOOL && (m?.toolCallId || m?.tool_call_id)) {
455
- out.toolCallId = String(m.toolCallId || m.tool_call_id);
456
- }
457
- return out;
458
- }
459
223
 
460
224
  const rl = createInterface({ input: process.stdin, terminal: false });
461
225
  rl.on("line", async (line) => {
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runClaudeCodeHook } from "./lib/run-hook.js";
4
+
5
+ runClaudeCodeHook("SubagentStop");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everme/claude-code",
3
- "version": "0.6.1",
3
+ "version": "0.6.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",
@@ -21,7 +21,7 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "@everme/agent-sdk": "^0.6.1"
24
+ "@everme/agent-sdk": "^0.6.2"
25
25
  },
26
26
  "keywords": [
27
27
  "evermind",
@@ -5,16 +5,11 @@ alwaysInclude: true
5
5
 
6
6
  # EverMe Memory Tools
7
7
 
8
- You have four canonical MCP tools for memory EverMe persists across past Claude Code sessions:
8
+ You have two MCP tools for memory EverMe persists across past Claude Code sessions. Both are read-only; the native hooks own every write.
9
9
 
10
- Recall:
11
10
  - `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
11
  - `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.
13
12
 
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.
17
-
18
13
  ## Dedupe protocol (hooks come first)
19
14
 
20
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:
@@ -24,10 +19,6 @@ The plugin's native hooks already inject `<everme_profile>` at session start and
24
19
  - Never repeat an identical query within the same turn.
25
20
  - Call `mem_context` only when no `<everme_profile>` block was injected this session.
26
21
 
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.
30
-
31
22
  ## Best practices
32
23
 
33
24
  1. Search with the user's specific terms first; only broaden if zero hits.