@echomem/mcp 1.4.39 → 1.4.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/hud/cli.js +25 -4
- package/dist/hud/hooks.js +58 -0
- package/dist/index.js +328 -244
- package/dist/package-metadata.js +2 -1
- package/dist/setup-page/client-extraction.js +9 -3
- package/dist/setup-page/styles-mvp.js +46 -0
- package/dist/setup.js +35 -16
- package/dist/source-session-hook.js +103 -0
- package/dist/source-session.js +261 -0
- package/dist/v1-contract.js +22 -0
- package/package.json +2 -2
- package/templates/echomem-recall.md +3 -0
- package/dist/forensics-10-problems.js +0 -633
package/README.md
CHANGED
|
@@ -74,6 +74,27 @@ installation, and `npx -y @echomem/mcp@latest update --all` remains the bootstra
|
|
|
74
74
|
Agents can still call `echo_context_health` for an on-demand local context-health report. It reads
|
|
75
75
|
the local Codex/Claude logs and does not require a separate process or desktop overlay.
|
|
76
76
|
|
|
77
|
+
## Local conversation lifecycle hooks
|
|
78
|
+
|
|
79
|
+
For hook-capable Codex and Claude Code clients, MCP setup installs two local lifecycle hooks:
|
|
80
|
+
|
|
81
|
+
- **`SessionStart` — source-session binding.** The hook receives the host-owned `session_id` and
|
|
82
|
+
`transcript_path`, verifies that they identify the same local JSONL, and idempotently binds the
|
|
83
|
+
canonical `codex:<id>` or `claude-code:<id>` context through
|
|
84
|
+
`POST /api/extension/source-sessions/bind`. On each MCP call, the bridge independently confirms
|
|
85
|
+
the exact host-owned session metadata and attaches the returned context before saving, recalling,
|
|
86
|
+
citing, sharing, or recording activity. No "latest session" lookup is used. `bind_source_session`
|
|
87
|
+
remains available only for clients without startup hooks.
|
|
88
|
+
- **`Stop` — private-save checkpoint.** Before a completed turn exits, the hook reads only that
|
|
89
|
+
turn's local transcript slice. If the turn appears durable and did not already call
|
|
90
|
+
`save_conversation`, it blocks completion once and asks the Agent to create a concise private
|
|
91
|
+
checkpoint. It never authors or uploads a checkpoint itself. Re-entry is guarded by
|
|
92
|
+
`stop_hook_active`, so the second Stop is allowed after the Agent has checked or saved.
|
|
93
|
+
|
|
94
|
+
Both hook entries are merged idempotently into `~/.codex/hooks.json` and
|
|
95
|
+
`~/.claude/settings.json`; unrelated user hooks and settings are preserved. Hooks fail open when
|
|
96
|
+
their local input is missing so they cannot prevent a conversation from starting or finishing.
|
|
97
|
+
|
|
77
98
|
## Agent Doctor — local workspace forensics (new in 1.4.9)
|
|
78
99
|
|
|
79
100
|
`echomem-mcp init` / `setup` now ends with an **Agent Doctor report**: a local scan of your
|
package/dist/hud/cli.js
CHANGED
|
@@ -5,12 +5,13 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { adapterList } from "./adapters.js";
|
|
6
6
|
import { autostartPlistPath, autostartSupported, disableAutostart, enableAutostart, isAutostartEnabled } from "./autostart.js";
|
|
7
7
|
import { statSignature } from "./fs.js";
|
|
8
|
-
import { installHooks, installSaveCheckpointHooks } from "./hooks.js";
|
|
8
|
+
import { installHooks, installSaveCheckpointHooks, installSourceSessionHooks } from "./hooks.js";
|
|
9
9
|
import { HudMonitor } from "./monitor.js";
|
|
10
10
|
import { renderStateText } from "./render.js";
|
|
11
11
|
import { renderReportText, runReport } from "./report.js";
|
|
12
12
|
import { createHudServer } from "./server.js";
|
|
13
13
|
import { runSaveCheckpointHook } from "../save-checkpoint-hook.js";
|
|
14
|
+
import { runSourceSessionStartHook } from "../source-session-hook.js";
|
|
14
15
|
const argv = process.argv.slice(2);
|
|
15
16
|
const command = argv[0] || "summary";
|
|
16
17
|
const flags = parseFlags(argv.slice(1));
|
|
@@ -27,8 +28,12 @@ try {
|
|
|
27
28
|
await cmdInstallHooks(flags);
|
|
28
29
|
else if (command === "install-save-hooks")
|
|
29
30
|
await cmdInstallSaveHooks(flags);
|
|
31
|
+
else if (command === "install-source-session-hooks")
|
|
32
|
+
await cmdInstallSourceSessionHooks(flags);
|
|
30
33
|
else if (command === "save-checkpoint")
|
|
31
34
|
await cmdSaveCheckpoint();
|
|
35
|
+
else if (command === "bind-source-session")
|
|
36
|
+
await cmdBindSourceSession();
|
|
32
37
|
else if (command === "autostart")
|
|
33
38
|
cmdAutostart(argv[1] || "status", flags);
|
|
34
39
|
else if (command === "status")
|
|
@@ -120,8 +125,17 @@ async function cmdInstallHooks(flags) {
|
|
|
120
125
|
console.log("Codex users: run /hooks in a new Codex session to review and trust changed hooks.");
|
|
121
126
|
}
|
|
122
127
|
async function cmdInstallSaveHooks(flags) {
|
|
123
|
-
const
|
|
124
|
-
|
|
128
|
+
const mode = parseMode(flags.client);
|
|
129
|
+
const paths = [...new Set([
|
|
130
|
+
...installSourceSessionHooks(mode),
|
|
131
|
+
...installSaveCheckpointHooks(mode),
|
|
132
|
+
])];
|
|
133
|
+
console.log(`Installed EchoMem source-session and private-save hooks:\n${paths.map((p) => `- ${p}`).join("\n")}`);
|
|
134
|
+
console.log("Codex users: run /hooks in a new Codex session to review and trust changed hooks.");
|
|
135
|
+
}
|
|
136
|
+
async function cmdInstallSourceSessionHooks(flags) {
|
|
137
|
+
const paths = installSourceSessionHooks(parseMode(flags.client));
|
|
138
|
+
console.log(`Installed EchoMem source-session hooks:\n${paths.map((p) => `- ${p}`).join("\n")}`);
|
|
125
139
|
console.log("Codex users: run /hooks in a new Codex session to review and trust changed hooks.");
|
|
126
140
|
}
|
|
127
141
|
async function cmdSaveCheckpoint() {
|
|
@@ -130,6 +144,12 @@ async function cmdSaveCheckpoint() {
|
|
|
130
144
|
input += String(chunk);
|
|
131
145
|
process.stdout.write(runSaveCheckpointHook(input));
|
|
132
146
|
}
|
|
147
|
+
async function cmdBindSourceSession() {
|
|
148
|
+
let input = "";
|
|
149
|
+
for await (const chunk of process.stdin)
|
|
150
|
+
input += String(chunk);
|
|
151
|
+
process.stdout.write(await runSourceSessionStartHook(input));
|
|
152
|
+
}
|
|
133
153
|
async function cmdReport(flags) {
|
|
134
154
|
const result = runReport(parseMode(flags.client), { limit: readNumber(flags.limit, 40) });
|
|
135
155
|
if (flags.json)
|
|
@@ -182,7 +202,8 @@ Usage:
|
|
|
182
202
|
echomem-hud serve [--client codex|claude-code|claude-desktop|both|auto] [--port 17377]
|
|
183
203
|
echomem-hud app [--client codex|claude-code|claude-desktop|both|auto]
|
|
184
204
|
echomem-hud install-hooks [--client codex|claude-code|both]
|
|
185
|
-
echomem-hud install-save-hooks [--client codex|claude-code|both]
|
|
205
|
+
echomem-hud install-save-hooks [--client codex|claude-code|both] (installs both lifecycle hooks)
|
|
206
|
+
echomem-hud install-source-session-hooks [--client codex|claude-code|both]
|
|
186
207
|
echomem-hud autostart on|off|status (show the HUD after restart — macOS)
|
|
187
208
|
echomem-hud status
|
|
188
209
|
echomem-hud report [--client codex|claude-code|claude-desktop|auto] [--limit 40] [--json]
|
package/dist/hud/hooks.js
CHANGED
|
@@ -22,6 +22,16 @@ export function installSaveCheckpointHooks(mode) {
|
|
|
22
22
|
}
|
|
23
23
|
return written;
|
|
24
24
|
}
|
|
25
|
+
export function installSourceSessionHooks(mode) {
|
|
26
|
+
const written = [];
|
|
27
|
+
if (mode === "codex" || mode === "both" || mode === "auto") {
|
|
28
|
+
written.push(installCodexSourceSessionHook());
|
|
29
|
+
}
|
|
30
|
+
if (mode === "claude-code" || mode === "both" || mode === "auto") {
|
|
31
|
+
written.push(installClaudeCodeSourceSessionHook());
|
|
32
|
+
}
|
|
33
|
+
return written;
|
|
34
|
+
}
|
|
25
35
|
function installCodexHooks() {
|
|
26
36
|
const dir = path.join(os.homedir(), ".codex");
|
|
27
37
|
const file = path.join(dir, "hooks.json");
|
|
@@ -39,6 +49,44 @@ function saveCheckpointCommand() {
|
|
|
39
49
|
const lifecycleCli = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
40
50
|
return `${JSON.stringify(process.execPath)} ${JSON.stringify(lifecycleCli)} save-checkpoint`;
|
|
41
51
|
}
|
|
52
|
+
function sourceSessionCommand() {
|
|
53
|
+
const lifecycleCli = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
54
|
+
return `${JSON.stringify(process.execPath)} ${JSON.stringify(lifecycleCli)} bind-source-session`;
|
|
55
|
+
}
|
|
56
|
+
function installCodexSourceSessionHook() {
|
|
57
|
+
const dir = path.join(os.homedir(), ".codex");
|
|
58
|
+
const file = path.join(dir, "hooks.json");
|
|
59
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
60
|
+
const content = readHooksFile(file);
|
|
61
|
+
content.hooks = content.hooks || {};
|
|
62
|
+
content.hooks.SessionStart = mergeSourceSessionGroup(content.hooks.SessionStart, {
|
|
63
|
+
hooks: [{
|
|
64
|
+
type: "command",
|
|
65
|
+
command: sourceSessionCommand(),
|
|
66
|
+
timeout: 5,
|
|
67
|
+
statusMessage: "Binding EchoMem to this conversation",
|
|
68
|
+
}],
|
|
69
|
+
});
|
|
70
|
+
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
|
71
|
+
return file;
|
|
72
|
+
}
|
|
73
|
+
function installClaudeCodeSourceSessionHook() {
|
|
74
|
+
const dir = path.join(os.homedir(), ".claude");
|
|
75
|
+
const file = path.join(dir, "settings.json");
|
|
76
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
77
|
+
const content = readHooksFile(file);
|
|
78
|
+
content.hooks = content.hooks || {};
|
|
79
|
+
content.hooks.SessionStart = mergeSourceSessionGroup(content.hooks.SessionStart, {
|
|
80
|
+
hooks: [{
|
|
81
|
+
type: "command",
|
|
82
|
+
command: sourceSessionCommand(),
|
|
83
|
+
timeout: 5,
|
|
84
|
+
statusMessage: "Binding EchoMem to this conversation",
|
|
85
|
+
}],
|
|
86
|
+
});
|
|
87
|
+
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
|
88
|
+
return file;
|
|
89
|
+
}
|
|
42
90
|
function installCodexSaveCheckpointHook() {
|
|
43
91
|
const dir = path.join(os.homedir(), ".codex");
|
|
44
92
|
const file = path.join(dir, "hooks.json");
|
|
@@ -97,11 +145,21 @@ function mergeSaveCheckpointGroup(existing, group) {
|
|
|
97
145
|
groups.push(group);
|
|
98
146
|
return groups;
|
|
99
147
|
}
|
|
148
|
+
function mergeSourceSessionGroup(existing, group) {
|
|
149
|
+
const groups = Array.isArray(existing) ? existing.filter((item) => !isEchoSourceSessionGroup(item)) : [];
|
|
150
|
+
groups.push(group);
|
|
151
|
+
return groups;
|
|
152
|
+
}
|
|
100
153
|
function isEchoHudGroup(value) {
|
|
101
154
|
if (typeof value !== "object" || value === null)
|
|
102
155
|
return false;
|
|
103
156
|
return JSON.stringify(value).includes("summary --client codex --json");
|
|
104
157
|
}
|
|
158
|
+
function isEchoSourceSessionGroup(value) {
|
|
159
|
+
if (typeof value !== "object" || value === null)
|
|
160
|
+
return false;
|
|
161
|
+
return JSON.stringify(value).includes("bind-source-session");
|
|
162
|
+
}
|
|
105
163
|
function isEchoSaveCheckpointGroup(value) {
|
|
106
164
|
if (typeof value !== "object" || value === null)
|
|
107
165
|
return false;
|