@echomem/mcp 1.4.44 → 1.4.46
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 +28 -25
- package/dist/city/README.md +9 -0
- package/dist/city/echo-ai-city-only.html +2232 -0
- package/dist/city/echo-extraction-plate.html +330 -0
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/city/personality_stickers/bossy.png +0 -0
- package/dist/city/personality_stickers/ghosty.png +0 -0
- package/dist/city/personality_stickers/loopy.png +0 -0
- package/dist/city/personality_stickers/lusty.png +0 -0
- package/dist/city/personality_stickers/maxxy.png +0 -0
- package/dist/city/personality_stickers/tabby.png +0 -0
- package/dist/city/vendor/OrbitControls.js +1417 -0
- package/dist/city/vendor/RoundedBoxGeometry.js +155 -0
- package/dist/city/vendor/echo_general-file-21.riv +0 -0
- package/dist/city/vendor/rive.js +8139 -0
- package/dist/city/vendor/rive.wasm +0 -0
- package/dist/city/vendor/three.module.min.js +6 -0
- package/dist/context-analysis/claude-native-canonical.js +2 -2
- package/dist/context-analysis/vendored-canonical.js +2 -2
- package/dist/context-analysis/workspace-report.js +3 -3
- package/dist/forensics.js +1531 -0
- package/dist/hud/hooks.js +31 -43
- package/dist/index.js +79 -15
- package/dist/local-data-paths.js +38 -0
- package/dist/migrate.js +140 -70
- package/dist/report.js +721 -0
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-core.js +372 -18
- package/dist/setup-page/client-extraction.js +204 -37
- package/dist/setup-page/client-lifecycle.js +101 -31
- package/dist/setup-page/client-report-audit.js +819 -0
- package/dist/setup-page/client-report-city.js +356 -0
- package/dist/setup-page/client-report.js +6 -0
- package/dist/setup-page/client.js +2 -0
- package/dist/setup-page/styles-city-report.js +880 -0
- package/dist/setup-page/styles-context-audit.js +470 -0
- package/dist/setup-page/styles-extraction.js +31 -1
- package/dist/setup-page/styles-foundation.js +89 -0
- package/dist/setup-page/styles-mvp.js +155 -10
- package/dist/setup-page/styles-website-alignment.js +204 -0
- package/dist/setup-page/styles.js +4 -0
- package/dist/setup-page.js +4 -4
- package/dist/setup-preview.js +212 -4
- package/dist/setup.js +793 -300
- package/dist/source-session.js +3 -9
- package/dist/v1-contract.js +8 -0
- package/package.json +7 -9
- package/dist/config-files.js +0 -63
- package/dist/local-jsonl.js +0 -87
- package/dist/onboarding-stats.js +0 -16
package/dist/hud/hooks.js
CHANGED
|
@@ -2,7 +2,6 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { atomicWriteJsonObject, readJsonObjectFile } from "../config-files.js";
|
|
6
5
|
export function installHooks(mode) {
|
|
7
6
|
const written = [];
|
|
8
7
|
if (mode === "codex" || mode === "both" || mode === "auto") {
|
|
@@ -34,16 +33,16 @@ export function installSourceSessionHooks(mode) {
|
|
|
34
33
|
return written;
|
|
35
34
|
}
|
|
36
35
|
function installCodexHooks() {
|
|
37
|
-
const dir =
|
|
36
|
+
const dir = path.join(os.homedir(), ".codex");
|
|
38
37
|
const file = path.join(dir, "hooks.json");
|
|
39
38
|
fs.mkdirSync(dir, { recursive: true });
|
|
40
39
|
const hookCommand = `${JSON.stringify(process.execPath)} ${JSON.stringify(process.argv[1])} summary --client codex --json`;
|
|
41
40
|
const content = readHooksFile(file);
|
|
42
41
|
content.hooks = content.hooks || {};
|
|
43
|
-
content.hooks.PostToolUse = mergeHookGroup(content.hooks.PostToolUse, { matcher: "*", hooks: [
|
|
44
|
-
content.hooks.PostCompact = mergeHookGroup(content.hooks.PostCompact, { hooks: [
|
|
45
|
-
content.hooks.Stop = mergeHookGroup(content.hooks.Stop, { hooks: [
|
|
46
|
-
|
|
42
|
+
content.hooks.PostToolUse = mergeHookGroup(content.hooks.PostToolUse, { matcher: "*", hooks: [{ type: "command", command: hookCommand, timeout: 5 }] });
|
|
43
|
+
content.hooks.PostCompact = mergeHookGroup(content.hooks.PostCompact, { hooks: [{ type: "command", command: hookCommand, timeout: 5 }] });
|
|
44
|
+
content.hooks.Stop = mergeHookGroup(content.hooks.Stop, { hooks: [{ type: "command", command: hookCommand, timeout: 5 }] });
|
|
45
|
+
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
|
47
46
|
return file;
|
|
48
47
|
}
|
|
49
48
|
function saveCheckpointCommand() {
|
|
@@ -55,19 +54,24 @@ function sourceSessionCommand() {
|
|
|
55
54
|
return `${JSON.stringify(process.execPath)} ${JSON.stringify(lifecycleCli)} bind-source-session`;
|
|
56
55
|
}
|
|
57
56
|
function installCodexSourceSessionHook() {
|
|
58
|
-
const dir =
|
|
57
|
+
const dir = path.join(os.homedir(), ".codex");
|
|
59
58
|
const file = path.join(dir, "hooks.json");
|
|
60
59
|
fs.mkdirSync(dir, { recursive: true });
|
|
61
60
|
const content = readHooksFile(file);
|
|
62
61
|
content.hooks = content.hooks || {};
|
|
63
62
|
content.hooks.SessionStart = mergeSourceSessionGroup(content.hooks.SessionStart, {
|
|
64
|
-
hooks: [
|
|
63
|
+
hooks: [{
|
|
64
|
+
type: "command",
|
|
65
|
+
command: sourceSessionCommand(),
|
|
66
|
+
timeout: 5,
|
|
67
|
+
statusMessage: "Binding EchoMem to this conversation",
|
|
68
|
+
}],
|
|
65
69
|
});
|
|
66
|
-
|
|
70
|
+
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
|
67
71
|
return file;
|
|
68
72
|
}
|
|
69
73
|
function installClaudeCodeSourceSessionHook() {
|
|
70
|
-
const dir =
|
|
74
|
+
const dir = path.join(os.homedir(), ".claude");
|
|
71
75
|
const file = path.join(dir, "settings.json");
|
|
72
76
|
fs.mkdirSync(dir, { recursive: true });
|
|
73
77
|
const content = readHooksFile(file);
|
|
@@ -80,23 +84,28 @@ function installClaudeCodeSourceSessionHook() {
|
|
|
80
84
|
statusMessage: "Binding EchoMem to this conversation",
|
|
81
85
|
}],
|
|
82
86
|
});
|
|
83
|
-
|
|
87
|
+
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
|
84
88
|
return file;
|
|
85
89
|
}
|
|
86
90
|
function installCodexSaveCheckpointHook() {
|
|
87
|
-
const dir =
|
|
91
|
+
const dir = path.join(os.homedir(), ".codex");
|
|
88
92
|
const file = path.join(dir, "hooks.json");
|
|
89
93
|
fs.mkdirSync(dir, { recursive: true });
|
|
90
94
|
const content = readHooksFile(file);
|
|
91
95
|
content.hooks = content.hooks || {};
|
|
92
96
|
content.hooks.Stop = mergeSaveCheckpointGroup(content.hooks.Stop, {
|
|
93
|
-
hooks: [
|
|
97
|
+
hooks: [{
|
|
98
|
+
type: "command",
|
|
99
|
+
command: saveCheckpointCommand(),
|
|
100
|
+
timeout: 10,
|
|
101
|
+
statusMessage: "Checking whether completed work should be remembered",
|
|
102
|
+
}],
|
|
94
103
|
});
|
|
95
|
-
|
|
104
|
+
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
|
96
105
|
return file;
|
|
97
106
|
}
|
|
98
107
|
function installClaudeCodeSaveCheckpointHook() {
|
|
99
|
-
const dir =
|
|
108
|
+
const dir = path.join(os.homedir(), ".claude");
|
|
100
109
|
const file = path.join(dir, "settings.json");
|
|
101
110
|
fs.mkdirSync(dir, { recursive: true });
|
|
102
111
|
const content = readHooksFile(file);
|
|
@@ -109,43 +118,22 @@ function installClaudeCodeSaveCheckpointHook() {
|
|
|
109
118
|
statusMessage: "Checking whether completed work should be remembered",
|
|
110
119
|
}],
|
|
111
120
|
});
|
|
112
|
-
|
|
121
|
+
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
|
113
122
|
return file;
|
|
114
123
|
}
|
|
115
124
|
function installClaudeCodeSnippet() {
|
|
116
|
-
const dir = path.join(
|
|
125
|
+
const dir = path.join(os.homedir(), ".claude", "echo-ctx");
|
|
117
126
|
fs.mkdirSync(dir, { recursive: true });
|
|
118
127
|
return dir;
|
|
119
128
|
}
|
|
120
129
|
function readHooksFile(file) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const home = os.homedir();
|
|
125
|
-
const configured = process.env[envKey]?.trim();
|
|
126
|
-
if (!configured)
|
|
127
|
-
return path.join(home, fallbackName);
|
|
128
|
-
if (configured === "~")
|
|
129
|
-
return home;
|
|
130
|
-
if (configured.startsWith("~/") || configured.startsWith("~\\")) {
|
|
131
|
-
return path.join(home, configured.slice(2));
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
132
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
catch {
|
|
135
|
+
return {};
|
|
135
136
|
}
|
|
136
|
-
return path.normalize(configured);
|
|
137
|
-
}
|
|
138
|
-
function codexCommandHook(command, timeout, statusMessage) {
|
|
139
|
-
return {
|
|
140
|
-
type: "command",
|
|
141
|
-
command,
|
|
142
|
-
// Codex supports an explicit Windows override. Keeping it identical is intentional: the
|
|
143
|
-
// absolute node.exe and CLI paths are already quoted, and Codex can select this field without
|
|
144
|
-
// interpreting a POSIX-only fallback.
|
|
145
|
-
commandWindows: command,
|
|
146
|
-
timeout,
|
|
147
|
-
...(statusMessage ? { statusMessage } : {}),
|
|
148
|
-
};
|
|
149
137
|
}
|
|
150
138
|
function mergeHookGroup(existing, group) {
|
|
151
139
|
const groups = Array.isArray(existing) ? existing.filter((item) => !isEchoHudGroup(item)) : [];
|
package/dist/index.js
CHANGED
|
@@ -7,11 +7,12 @@ import { ZodError } from "zod";
|
|
|
7
7
|
import { bindSourceSessionSchema, canonicalToolNames, completeGroupPublicationSchema, createGroupInviteSchema, createGroupSchema, deleteMemorySchema, flagPublicationAttentionSchema, getGroupSessionSharingSchema, getByContextSchema, groupContextSchema, joinGroupSchema, keywordsSchema, listFriendsSchema, listToolSpecs, othersSchema, publishBatchToGroupSchema, publishToGroupSchema, prepareGroupPublicationSchema, publicMemorySchema, recordMemoryCitationsSchema, requestGroupSessionSharingSchema, resolveCanonicalToolName, saveConversationSchema, searchMemoriesSchema, searchUsersSchema, sendFriendRequestSchema, timeRangeSchema, updateGroupProfileSchema, setGroupSessionSharingSchema, } from "./v1-contract.js";
|
|
8
8
|
import { KeyStore } from "./keystore.js";
|
|
9
9
|
import { EventLogger, hashText } from "./events.js";
|
|
10
|
+
import { buildReportText } from "./report.js";
|
|
10
11
|
import { contextHealthMarkdown, recomposeCapsuleMarkdown } from "./hud/api.js";
|
|
11
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
14
|
import { fetchEncryptionConfig, decryptMemoryFields, verifyKeyB64, } from "./encryption.js";
|
|
14
|
-
import {
|
|
15
|
+
import { runCli } from "./setup.js";
|
|
15
16
|
import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS, MEMORY_CITATION_INSTRUCTION, SAVED_MEMORY_RECEIPT_INSTRUCTION, } from "./package-metadata.js";
|
|
16
17
|
import { clearBillingAlert, writeBillingAlert } from "./billing-alert.js";
|
|
17
18
|
import { checkLatestUpdateStatus, formatUpdateNotice, formatUpdateStatusText, startBackgroundUpdateCheck, } from "./update-check.js";
|
|
@@ -612,6 +613,8 @@ function toolEventName(canonicalName, status) {
|
|
|
612
613
|
class EchoMemApiClient {
|
|
613
614
|
store;
|
|
614
615
|
axios;
|
|
616
|
+
activeToken;
|
|
617
|
+
accountGeneration = 0;
|
|
615
618
|
boundSourceSession = null;
|
|
616
619
|
requestSourceSession = new AsyncLocalStorage();
|
|
617
620
|
sourceSessionsByCanonicalKey = new Map();
|
|
@@ -622,6 +625,7 @@ class EchoMemApiClient {
|
|
|
622
625
|
deleteConfirmations = new Map();
|
|
623
626
|
constructor(store) {
|
|
624
627
|
this.store = store;
|
|
628
|
+
this.activeToken = store.getToken();
|
|
625
629
|
this.axios = axios.create({
|
|
626
630
|
baseURL: ECHO_API_BASE_URL.replace(/\/$/, ""),
|
|
627
631
|
headers: {
|
|
@@ -631,7 +635,7 @@ class EchoMemApiClient {
|
|
|
631
635
|
// Read the token fresh on EVERY request (not baked in at construction) so a `login` that runs
|
|
632
636
|
// after the editor already launched the bridge is picked up on the next call — no restart.
|
|
633
637
|
this.axios.interceptors.request.use((config) => {
|
|
634
|
-
const token = this.
|
|
638
|
+
const token = this.synchronizeAccountContext();
|
|
635
639
|
if (token)
|
|
636
640
|
config.headers.set("Authorization", `Bearer ${token}`);
|
|
637
641
|
const sourceSession = this.getBoundSourceSession();
|
|
@@ -641,21 +645,46 @@ class EchoMemApiClient {
|
|
|
641
645
|
return config;
|
|
642
646
|
});
|
|
643
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Account-scoped state must move atomically with the credential file. The token is intentionally
|
|
650
|
+
* read fresh, but encryption, identity, source-session, and confirmation caches belong to the
|
|
651
|
+
* token that populated them and cannot survive an in-process account switch.
|
|
652
|
+
*/
|
|
653
|
+
synchronizeAccountContext() {
|
|
654
|
+
const token = this.store.getToken();
|
|
655
|
+
if (token === this.activeToken)
|
|
656
|
+
return token;
|
|
657
|
+
this.activeToken = token;
|
|
658
|
+
this.accountGeneration += 1;
|
|
659
|
+
this.encConfigPromise = null;
|
|
660
|
+
this.whoamiCache = null;
|
|
661
|
+
this.boundSourceSession = null;
|
|
662
|
+
this.sourceSessionsByCanonicalKey.clear();
|
|
663
|
+
this.deleteConfirmations.clear();
|
|
664
|
+
return token;
|
|
665
|
+
}
|
|
666
|
+
/** Lets the MCP server invalidate its account-scoped tool-description caches too. */
|
|
667
|
+
refreshAccountContext() {
|
|
668
|
+
this.synchronizeAccountContext();
|
|
669
|
+
return this.accountGeneration;
|
|
670
|
+
}
|
|
644
671
|
/** Whether a usable API token currently exists (read fresh from the keystore each call). */
|
|
645
672
|
hasToken() {
|
|
646
|
-
return !!this.
|
|
673
|
+
return !!this.synchronizeAccountContext();
|
|
647
674
|
}
|
|
648
675
|
/** The per-process session id — the join key telemetry shares with grouped saves. */
|
|
649
676
|
getSessionId() {
|
|
650
677
|
return this.sessionId;
|
|
651
678
|
}
|
|
652
679
|
getBoundSourceSession() {
|
|
680
|
+
this.synchronizeAccountContext();
|
|
653
681
|
return this.requestSourceSession.getStore() ?? this.boundSourceSession;
|
|
654
682
|
}
|
|
655
683
|
async withSourceSession(sourceSession, operation) {
|
|
656
684
|
return sourceSession ? this.requestSourceSession.run(sourceSession, operation) : operation();
|
|
657
685
|
}
|
|
658
686
|
async bindSourceSession(verified, persistForBridge = true) {
|
|
687
|
+
this.synchronizeAccountContext();
|
|
659
688
|
const cached = this.sourceSessionsByCanonicalKey.get(verified.canonicalKey);
|
|
660
689
|
if (cached) {
|
|
661
690
|
if (persistForBridge)
|
|
@@ -738,16 +767,23 @@ class EchoMemApiClient {
|
|
|
738
767
|
return undefined;
|
|
739
768
|
}
|
|
740
769
|
}
|
|
741
|
-
/** Encryption config for the account,
|
|
770
|
+
/** Encryption config for the current account, cached only for the lifetime of its token. */
|
|
742
771
|
async getEncryptionConfig() {
|
|
772
|
+
const generation = this.refreshAccountContext();
|
|
743
773
|
if (!this.encConfigPromise) {
|
|
744
|
-
|
|
745
|
-
this.encConfigPromise
|
|
774
|
+
const request = fetchEncryptionConfig(this.axios).catch((error) => {
|
|
775
|
+
if (this.encConfigPromise === request)
|
|
776
|
+
this.encConfigPromise = null; // allow retry next call
|
|
746
777
|
console.error(`[encryption] config fetch failed: ${describeError(error)} — refusing memory access`);
|
|
747
778
|
throw new EncryptionStatusUnavailableError("EchoMem could not verify the account encryption state");
|
|
748
779
|
});
|
|
780
|
+
this.encConfigPromise = request;
|
|
749
781
|
}
|
|
750
|
-
|
|
782
|
+
const request = this.encConfigPromise;
|
|
783
|
+
const config = await request;
|
|
784
|
+
if (this.refreshAccountContext() !== generation)
|
|
785
|
+
return this.getEncryptionConfig();
|
|
786
|
+
return config;
|
|
751
787
|
}
|
|
752
788
|
/**
|
|
753
789
|
* Resolve the encryption state for a read/write. For encrypted accounts this REQUIRES a usable
|
|
@@ -755,13 +791,19 @@ class EchoMemApiClient {
|
|
|
755
791
|
* handing the model ciphertext. For unencrypted accounts it returns `{ enabled: false }`.
|
|
756
792
|
*/
|
|
757
793
|
async encState() {
|
|
794
|
+
const generation = this.refreshAccountContext();
|
|
758
795
|
const cfg = await this.getEncryptionConfig();
|
|
796
|
+
if (this.refreshAccountContext() !== generation)
|
|
797
|
+
return this.encState();
|
|
759
798
|
if (!cfg.enabled)
|
|
760
799
|
return { enabled: false };
|
|
761
800
|
const key = this.store.getKey();
|
|
762
801
|
if (!key)
|
|
763
802
|
throw new LockedError("EchoMem vault locked");
|
|
764
|
-
|
|
803
|
+
const isValid = await verifyKeyB64(key, cfg);
|
|
804
|
+
if (this.refreshAccountContext() !== generation)
|
|
805
|
+
return this.encState();
|
|
806
|
+
if (!isValid) {
|
|
765
807
|
// A file-backed stale key can be removed safely; environment-provided
|
|
766
808
|
// keys remain under the caller's control and are ignored until corrected.
|
|
767
809
|
if (!process.env.ECHO_ENCRYPTION_KEY)
|
|
@@ -778,16 +820,23 @@ class EchoMemApiClient {
|
|
|
778
820
|
}
|
|
779
821
|
}
|
|
780
822
|
async whoami() {
|
|
823
|
+
const generation = this.refreshAccountContext();
|
|
781
824
|
if (!this.whoamiCache) {
|
|
782
|
-
|
|
825
|
+
const request = this.axios
|
|
783
826
|
.get("/api/openclaw/v1/whoami", { timeout: 6000 })
|
|
784
827
|
.then((response) => response.data)
|
|
785
828
|
.catch((error) => {
|
|
786
|
-
this.whoamiCache
|
|
829
|
+
if (this.whoamiCache === request)
|
|
830
|
+
this.whoamiCache = null;
|
|
787
831
|
throw error;
|
|
788
832
|
});
|
|
833
|
+
this.whoamiCache = request;
|
|
789
834
|
}
|
|
790
|
-
|
|
835
|
+
const request = this.whoamiCache;
|
|
836
|
+
const identity = await request;
|
|
837
|
+
if (this.refreshAccountContext() !== generation)
|
|
838
|
+
return this.whoami();
|
|
839
|
+
return identity;
|
|
791
840
|
}
|
|
792
841
|
async fetchMemoryById(id, enc) {
|
|
793
842
|
try {
|
|
@@ -1254,6 +1303,7 @@ function capWait(pending) {
|
|
|
1254
1303
|
class EchoMemMCPServer {
|
|
1255
1304
|
server;
|
|
1256
1305
|
client;
|
|
1306
|
+
accountGeneration = 0;
|
|
1257
1307
|
mapCache = null;
|
|
1258
1308
|
groupMapCache = null;
|
|
1259
1309
|
events;
|
|
@@ -1326,8 +1376,19 @@ class EchoMemMCPServer {
|
|
|
1326
1376
|
platform_source: hostPlatform,
|
|
1327
1377
|
};
|
|
1328
1378
|
}
|
|
1379
|
+
refreshAccountContext() {
|
|
1380
|
+
const generation = this.client.refreshAccountContext();
|
|
1381
|
+
if (generation === this.accountGeneration)
|
|
1382
|
+
return;
|
|
1383
|
+
this.accountGeneration = generation;
|
|
1384
|
+
this.mapCache = null;
|
|
1385
|
+
this.groupMapCache = null;
|
|
1386
|
+
this.mapInjected = false;
|
|
1387
|
+
this.groupMapInjected = false;
|
|
1388
|
+
}
|
|
1329
1389
|
setupToolHandlers() {
|
|
1330
1390
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1391
|
+
this.refreshAccountContext();
|
|
1331
1392
|
const clientVersion = this.server.getClientVersion();
|
|
1332
1393
|
this.mcpClientName = clientVersion?.name ?? this.mcpClientName;
|
|
1333
1394
|
this.mcpClientVersion = clientVersion?.version ?? this.mcpClientVersion;
|
|
@@ -1359,6 +1420,7 @@ class EchoMemMCPServer {
|
|
|
1359
1420
|
return { tools: listToolSpecs({ map, groupMap, updateNotice }) };
|
|
1360
1421
|
});
|
|
1361
1422
|
this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
1423
|
+
this.refreshAccountContext();
|
|
1362
1424
|
const resolvedCanonicalName = resolveCanonicalToolName(request.params.name);
|
|
1363
1425
|
const recallRoute = routePersonalRecallInvocation(resolvedCanonicalName, request.params.arguments);
|
|
1364
1426
|
const canonicalName = recallRoute.canonicalName;
|
|
@@ -1410,6 +1472,10 @@ class EchoMemMCPServer {
|
|
|
1410
1472
|
if (recallRoute.error) {
|
|
1411
1473
|
throw new McpError(ErrorCode.InvalidParams, recallRoute.error);
|
|
1412
1474
|
}
|
|
1475
|
+
// The usage report is a local, $0 audit — works with no login (value before signup).
|
|
1476
|
+
if (canonicalName === canonicalToolNames.report) {
|
|
1477
|
+
return { content: [{ type: "text", text: await buildReportText(false) }] };
|
|
1478
|
+
}
|
|
1413
1479
|
if (canonicalName === canonicalToolNames.updateStatus) {
|
|
1414
1480
|
if (DESKTOP_MANAGED) {
|
|
1415
1481
|
return {
|
|
@@ -1605,7 +1671,8 @@ class EchoMemMCPServer {
|
|
|
1605
1671
|
rec.ok = rec.error_kind === "none";
|
|
1606
1672
|
rec.latency_ms = Date.now() - t0;
|
|
1607
1673
|
this.events.record(rec);
|
|
1608
|
-
if (canonicalName !== canonicalToolNames.
|
|
1674
|
+
if (canonicalName !== canonicalToolNames.report &&
|
|
1675
|
+
canonicalName !== canonicalToolNames.updateStatus &&
|
|
1609
1676
|
canonicalName !== canonicalToolNames.contextHealth &&
|
|
1610
1677
|
canonicalName !== canonicalToolNames.recompose &&
|
|
1611
1678
|
canonicalName !== canonicalToolNames.save &&
|
|
@@ -2697,9 +2764,6 @@ async function main() {
|
|
|
2697
2764
|
// inherited descriptor — can't leave the terminal hanging after setup/migrate completes.
|
|
2698
2765
|
process.exit(process.exitCode ?? 0);
|
|
2699
2766
|
}
|
|
2700
|
-
// Existing standalone users may receive a new runtime without re-running setup. Refresh only
|
|
2701
|
-
// EchoMem's marker-owned block so stale Desktop-era instructions disappear automatically.
|
|
2702
|
-
refreshInstalledMemoryGuidance();
|
|
2703
2767
|
const store = new KeyStore();
|
|
2704
2768
|
// Serve mode is meant to be SPAWNED by the MCP client (editor), which drives us over a piped stdin.
|
|
2705
2769
|
// A human who runs the bare server in a terminal instead gets a process that blocks forever on stdio
|
package/dist/local-data-paths.js
CHANGED
|
@@ -85,3 +85,41 @@ export function resolveClaudeProjectsDir(opts = {}) {
|
|
|
85
85
|
const root = configuredRoot("CLAUDE_CONFIG_DIR", ".claude", opts);
|
|
86
86
|
return resolveReadableDirectory(root ? path.join(root, "projects") : null);
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve Claude Desktop's local Cowork sandbox root.
|
|
90
|
+
*
|
|
91
|
+
* Cowork does not write into the normal `~/.claude/projects` profile. Claude Desktop creates an
|
|
92
|
+
* OS-specific support directory and stores each local-agent session below
|
|
93
|
+
* `local-agent-mode-sessions/<org>/<conversation>/local_<id>/`. The actual transcript is nested
|
|
94
|
+
* again under that sandbox's `.claude/projects` directory.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveClaudeCoworkSessionsDir(opts = {}) {
|
|
97
|
+
const homeDir = normalizedHomeDir(opts.homeDir ?? os.homedir());
|
|
98
|
+
if (!homeDir)
|
|
99
|
+
return null;
|
|
100
|
+
const env = opts.env ?? process.env;
|
|
101
|
+
const configured = env.CLAUDE_DESKTOP_SUPPORT_DIR;
|
|
102
|
+
let supportRoot = null;
|
|
103
|
+
if (typeof configured === "string" && configured.trim()) {
|
|
104
|
+
supportRoot = expandCurrentUserHome(configured, homeDir);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const platform = opts.platform ?? process.platform;
|
|
108
|
+
if (platform === "darwin") {
|
|
109
|
+
supportRoot = path.join(homeDir, "Library", "Application Support", "Claude");
|
|
110
|
+
}
|
|
111
|
+
else if (platform === "win32") {
|
|
112
|
+
const appData = typeof env.APPDATA === "string" && env.APPDATA.trim()
|
|
113
|
+
? expandCurrentUserHome(env.APPDATA, homeDir)
|
|
114
|
+
: path.join(homeDir, "AppData", "Roaming");
|
|
115
|
+
supportRoot = appData ? path.join(appData, "Claude") : null;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const configHome = typeof env.XDG_CONFIG_HOME === "string" && env.XDG_CONFIG_HOME.trim()
|
|
119
|
+
? expandCurrentUserHome(env.XDG_CONFIG_HOME, homeDir)
|
|
120
|
+
: path.join(homeDir, ".config");
|
|
121
|
+
supportRoot = configHome ? path.join(configHome, "Claude") : null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return resolveReadableDirectory(supportRoot ? path.join(supportRoot, "local-agent-mode-sessions") : null);
|
|
125
|
+
}
|