@echomem/mcp 1.4.28 → 1.4.30
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/dist/index.js +114 -17
- package/dist/package-metadata.js +2 -0
- package/dist/setup-page/client-core.js +6 -7
- package/dist/setup-page/client-extraction.js +10 -2
- package/dist/setup-page/styles-mvp.js +50 -0
- package/dist/setup-preview.js +11 -0
- package/dist/setup.js +1 -0
- package/dist/v1-contract.js +51 -10
- package/package.json +2 -2
- package/templates/codex-skills/echomem-search/SKILL.md +3 -2
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import axios from "axios";
|
|
6
6
|
import { ZodError } from "zod";
|
|
7
|
-
import { canonicalToolNames, completeGroupPublicationSchema, createGroupInviteSchema, createGroupSchema, deleteMemorySchema, flagPublicationAttentionSchema, getGroupSessionSharingSchema, getByContextSchema, groupContextSchema, joinGroupSchema, keywordsSchema, listFriendsSchema, listToolSpecs, othersSchema, publishBatchToGroupSchema, publishToGroupSchema, prepareGroupPublicationSchema, publicMemorySchema, resolveCanonicalToolName, saveConversationSchema, searchMemoriesSchema, searchUsersSchema, sendFriendRequestSchema, timeRangeSchema, updateGroupProfileSchema, setGroupSessionSharingSchema, } from "./v1-contract.js";
|
|
7
|
+
import { canonicalToolNames, completeGroupPublicationSchema, createGroupInviteSchema, createGroupSchema, deleteMemorySchema, flagPublicationAttentionSchema, getGroupSessionSharingSchema, getByContextSchema, groupContextSchema, joinGroupSchema, keywordsSchema, listFriendsSchema, listToolSpecs, othersSchema, publishBatchToGroupSchema, publishToGroupSchema, prepareGroupPublicationSchema, publicMemorySchema, recordMemoryCitationsSchema, 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
10
|
import { buildReportText } from "./report.js";
|
|
@@ -12,7 +12,7 @@ import { contextHealthMarkdown, recomposeCapsuleMarkdown } from "./hud/api.js";
|
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import { fetchEncryptionConfig, decryptMemoryFields, verifyKeyB64, } from "./encryption.js";
|
|
14
14
|
import { runCli } from "./setup.js";
|
|
15
|
-
import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS } from "./package-metadata.js";
|
|
15
|
+
import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS, MEMORY_CITATION_INSTRUCTION, } from "./package-metadata.js";
|
|
16
16
|
import { clearBillingAlert, writeBillingAlert } from "./billing-alert.js";
|
|
17
17
|
import { checkLatestUpdateStatus, formatUpdateNotice, formatUpdateStatusText, startBackgroundUpdateCheck, } from "./update-check.js";
|
|
18
18
|
const ECHO_API_BASE_URL = process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app";
|
|
@@ -32,6 +32,9 @@ function memoryMarkdownLink(url, keys, description) {
|
|
|
32
32
|
.replace(/\]/g, "\\]");
|
|
33
33
|
return `[${label}](${url})`;
|
|
34
34
|
}
|
|
35
|
+
function withMemoryCitationInstruction(text) {
|
|
36
|
+
return `${text}\n\n${MEMORY_CITATION_INSTRUCTION}`;
|
|
37
|
+
}
|
|
35
38
|
/** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
|
|
36
39
|
class NoTokenError extends Error {
|
|
37
40
|
}
|
|
@@ -510,6 +513,16 @@ function inputAnalyticsForTool(canonicalName, args) {
|
|
|
510
513
|
memory_id_hash: memoryId ? hashText(memoryId) : undefined,
|
|
511
514
|
};
|
|
512
515
|
}
|
|
516
|
+
case canonicalToolNames.recordCitations: {
|
|
517
|
+
const memoryIds = Array.isArray(a.memoryIds)
|
|
518
|
+
? a.memoryIds.filter((id) => typeof id === "string")
|
|
519
|
+
: [];
|
|
520
|
+
const receiptId = readString(a, "receiptId");
|
|
521
|
+
return {
|
|
522
|
+
memory_count: memoryIds.length,
|
|
523
|
+
receipt_id_hash: receiptId ? hashText(receiptId) : undefined,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
513
526
|
case canonicalToolNames.flagPublicationAttention: {
|
|
514
527
|
const memoryIds = Array.isArray(a.memoryIds) ? a.memoryIds.filter((id) => typeof id === "string") : [];
|
|
515
528
|
return {
|
|
@@ -607,6 +620,21 @@ class EchoMemApiClient {
|
|
|
607
620
|
return undefined;
|
|
608
621
|
}
|
|
609
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Compact "who covers what" guide for the user's company group, used to decorate the group tool
|
|
625
|
+
* descriptions. Group publication snapshots are plaintext by design, so this works even when the
|
|
626
|
+
* local vault is locked — unlike `fetchMemoryMap`, it never needs a decryption key.
|
|
627
|
+
*/
|
|
628
|
+
async fetchGroupMemoryMap() {
|
|
629
|
+
try {
|
|
630
|
+
const response = await this.axios.get("/api/extension/social/groups/current", { timeout: 6000 });
|
|
631
|
+
const map = typeof response.data?.map === "string" ? response.data.map.trim() : "";
|
|
632
|
+
return map || undefined;
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
return undefined;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
610
638
|
/** Encryption config for the account, fetched once and cached. Failures are not cached. */
|
|
611
639
|
async getEncryptionConfig() {
|
|
612
640
|
if (!this.encConfigPromise) {
|
|
@@ -980,6 +1008,16 @@ class EchoMemApiClient {
|
|
|
980
1008
|
throw new Error(`get_public_memory failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
|
|
981
1009
|
}
|
|
982
1010
|
}
|
|
1011
|
+
async recordMemoryCitations(args) {
|
|
1012
|
+
const parsed = recordMemoryCitationsSchema.parse(args ?? {});
|
|
1013
|
+
try {
|
|
1014
|
+
const response = await this.axios.post("/api/extension/social/memory-citations", { ...parsed, sessionKey: this.sessionId });
|
|
1015
|
+
return response.data;
|
|
1016
|
+
}
|
|
1017
|
+
catch (error) {
|
|
1018
|
+
throw new Error(`record_memory_citations failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
983
1021
|
async getGroupContext(args) {
|
|
984
1022
|
groupContextSchema.parse(args ?? {});
|
|
985
1023
|
try {
|
|
@@ -1073,15 +1111,28 @@ class EchoMemApiClient {
|
|
|
1073
1111
|
}
|
|
1074
1112
|
}
|
|
1075
1113
|
const SERVER_VERSION = MCP_PACKAGE_VERSION;
|
|
1114
|
+
/** Tool-description decoration must never delay the MCP handshake; give up and decorate next listing. */
|
|
1115
|
+
const MAP_WAIT_MS = 2500;
|
|
1116
|
+
function capWait(pending) {
|
|
1117
|
+
if (!pending)
|
|
1118
|
+
return Promise.resolve(undefined);
|
|
1119
|
+
return Promise.race([
|
|
1120
|
+
pending,
|
|
1121
|
+
new Promise((resolve) => setTimeout(() => resolve(undefined), MAP_WAIT_MS)),
|
|
1122
|
+
]);
|
|
1123
|
+
}
|
|
1076
1124
|
class EchoMemMCPServer {
|
|
1077
1125
|
server;
|
|
1078
1126
|
client;
|
|
1079
1127
|
mapCache = null;
|
|
1128
|
+
groupMapCache = null;
|
|
1080
1129
|
events;
|
|
1081
1130
|
mcpClientName;
|
|
1082
1131
|
mcpClientVersion;
|
|
1083
1132
|
/** Whether the most recent ListTools response carried the memory map (per-session recall signal). */
|
|
1084
1133
|
mapInjected = false;
|
|
1134
|
+
/** Whether the most recent ListTools response carried the group memory map. */
|
|
1135
|
+
groupMapInjected = false;
|
|
1085
1136
|
updateStatus;
|
|
1086
1137
|
constructor(store) {
|
|
1087
1138
|
this.server = new Server({
|
|
@@ -1130,15 +1181,23 @@ class EchoMemMCPServer {
|
|
|
1130
1181
|
this.mapCache.then((m) => { if (!m)
|
|
1131
1182
|
this.mapCache = null; }).catch(() => { this.mapCache = null; });
|
|
1132
1183
|
}
|
|
1184
|
+
if (this.client.hasToken() && !this.groupMapCache) {
|
|
1185
|
+
this.groupMapCache = this.client.fetchGroupMemoryMap();
|
|
1186
|
+
this.groupMapCache.then((m) => { if (!m)
|
|
1187
|
+
this.groupMapCache = null; }).catch(() => { this.groupMapCache = null; });
|
|
1188
|
+
}
|
|
1133
1189
|
// NEVER block tool-listing on the network. A flaky/unreachable API would otherwise hang the MCP
|
|
1134
|
-
// handshake and freeze the whole agent ("connection timed out after 30000ms"). The
|
|
1135
|
-
// best-effort: cap the wait, and
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1190
|
+
// handshake and freeze the whole agent ("connection timed out after 30000ms"). The maps are
|
|
1191
|
+
// best-effort: cap the wait, and they'll be injected on the next listing once they resolve.
|
|
1192
|
+
// Both races share one wall-clock budget because they run concurrently.
|
|
1193
|
+
const [map, groupMap] = await Promise.all([
|
|
1194
|
+
capWait(this.mapCache),
|
|
1195
|
+
capWait(this.groupMapCache),
|
|
1196
|
+
]);
|
|
1139
1197
|
this.mapInjected = !!map;
|
|
1198
|
+
this.groupMapInjected = !!groupMap;
|
|
1140
1199
|
const updateNotice = formatUpdateNotice(this.updateStatus);
|
|
1141
|
-
return { tools: listToolSpecs({ map, updateNotice }) };
|
|
1200
|
+
return { tools: listToolSpecs({ map, groupMap, updateNotice }) };
|
|
1142
1201
|
});
|
|
1143
1202
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1144
1203
|
const canonicalName = resolveCanonicalToolName(request.params.name);
|
|
@@ -1165,6 +1224,7 @@ class EchoMemMCPServer {
|
|
|
1165
1224
|
type: "tool_call",
|
|
1166
1225
|
tool: canonicalName,
|
|
1167
1226
|
map_injected: this.mapInjected,
|
|
1227
|
+
group_map_injected: this.groupMapInjected,
|
|
1168
1228
|
};
|
|
1169
1229
|
try {
|
|
1170
1230
|
// The usage report is a local, $0 audit — works with no login (value before signup).
|
|
@@ -1248,6 +1308,8 @@ class EchoMemMCPServer {
|
|
|
1248
1308
|
return await this.handleOthers(request.params.arguments);
|
|
1249
1309
|
case canonicalToolNames.publicMemory:
|
|
1250
1310
|
return await this.handlePublicMemory(request.params.arguments);
|
|
1311
|
+
case canonicalToolNames.recordCitations:
|
|
1312
|
+
return await this.handleRecordMemoryCitations(request.params.arguments);
|
|
1251
1313
|
case canonicalToolNames.groupContext:
|
|
1252
1314
|
return await this.handleGroupContext(request.params.arguments);
|
|
1253
1315
|
case canonicalToolNames.getGroupSessionSharing:
|
|
@@ -1446,7 +1508,12 @@ class EchoMemMCPServer {
|
|
|
1446
1508
|
].filter(Boolean).join("\n");
|
|
1447
1509
|
})
|
|
1448
1510
|
.join("\n\n");
|
|
1449
|
-
return {
|
|
1511
|
+
return {
|
|
1512
|
+
content: [{
|
|
1513
|
+
type: "text",
|
|
1514
|
+
text: withMemoryCitationInstruction(`Retrieved ${memories.length} memories:\n\n${formattedResults}`),
|
|
1515
|
+
}],
|
|
1516
|
+
};
|
|
1450
1517
|
}
|
|
1451
1518
|
// Fallback: untuned / time-range shape.
|
|
1452
1519
|
const { success, memories, error } = result;
|
|
@@ -1463,7 +1530,12 @@ Category: ${m.category} | Object: ${m.object} | Emotion: ${m.emotion}
|
|
|
1463
1530
|
Description: ${m.description}
|
|
1464
1531
|
Details: ${m.details || "N/A"}`)
|
|
1465
1532
|
.join("\n\n");
|
|
1466
|
-
return {
|
|
1533
|
+
return {
|
|
1534
|
+
content: [{
|
|
1535
|
+
type: "text",
|
|
1536
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} relevant memories:\n\n${formattedResults}`),
|
|
1537
|
+
}],
|
|
1538
|
+
};
|
|
1467
1539
|
}
|
|
1468
1540
|
async handleSave(args, rec) {
|
|
1469
1541
|
const sourceFallback = this.getMcpClientAnalytics().platform_source;
|
|
@@ -1565,7 +1637,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1565
1637
|
content: [
|
|
1566
1638
|
{
|
|
1567
1639
|
type: "text",
|
|
1568
|
-
text: `Found ${memories.length} memories between ${parsed.startDate} and ${parsed.endDate}:\n\n${formattedResults}
|
|
1640
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} memories between ${parsed.startDate} and ${parsed.endDate}:\n\n${formattedResults}`),
|
|
1569
1641
|
},
|
|
1570
1642
|
],
|
|
1571
1643
|
};
|
|
@@ -1591,7 +1663,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1591
1663
|
content: [
|
|
1592
1664
|
{
|
|
1593
1665
|
type: "text",
|
|
1594
|
-
text: `Recalled ${memories.length} memories from context ${parsed.contextId} (deterministic full batch):\n\n${formattedResults}
|
|
1666
|
+
text: withMemoryCitationInstruction(`Recalled ${memories.length} memories from context ${parsed.contextId} (deterministic full batch):\n\n${formattedResults}`),
|
|
1595
1667
|
},
|
|
1596
1668
|
],
|
|
1597
1669
|
};
|
|
@@ -1636,7 +1708,10 @@ Details: ${m.details || "N/A"}`)
|
|
|
1636
1708
|
const desc = readString(memory, "description") ?? "";
|
|
1637
1709
|
const details = compactOneLine(readString(memory, "details"), 260);
|
|
1638
1710
|
const id = readString(memory, "id");
|
|
1639
|
-
|
|
1711
|
+
const linkedTitle = id
|
|
1712
|
+
? memoryMarkdownLink(personalMemoryWebUrl(id), title, desc)
|
|
1713
|
+
: title;
|
|
1714
|
+
lines.push(`- ${linkedTitle}${id ? ` (${id})` : ""}: ${desc}${details ? ` — ${details}` : ""}`);
|
|
1640
1715
|
}
|
|
1641
1716
|
lines.push("");
|
|
1642
1717
|
}
|
|
@@ -1645,7 +1720,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1645
1720
|
content: [
|
|
1646
1721
|
{
|
|
1647
1722
|
type: "text",
|
|
1648
|
-
text: lines.join("\n"),
|
|
1723
|
+
text: withMemoryCitationInstruction(lines.join("\n")),
|
|
1649
1724
|
},
|
|
1650
1725
|
],
|
|
1651
1726
|
};
|
|
@@ -1672,7 +1747,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1672
1747
|
content: [
|
|
1673
1748
|
{
|
|
1674
1749
|
type: "text",
|
|
1675
|
-
text: `Found ${memories.length} memories matching keywords:\n\n${formattedResults}
|
|
1750
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} memories matching keywords:\n\n${formattedResults}`),
|
|
1676
1751
|
},
|
|
1677
1752
|
],
|
|
1678
1753
|
};
|
|
@@ -1729,7 +1804,7 @@ Details: ${m.details || "N/A"}`;
|
|
|
1729
1804
|
content: [
|
|
1730
1805
|
{
|
|
1731
1806
|
type: "text",
|
|
1732
|
-
text: `Found ${memories.length} others' public memories${metadata ? ` (${metadata})` : ""}:\n\n${formattedResults}
|
|
1807
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} others' public memories${metadata ? ` (${metadata})` : ""}:\n\n${formattedResults}`),
|
|
1733
1808
|
},
|
|
1734
1809
|
],
|
|
1735
1810
|
};
|
|
@@ -1857,9 +1932,31 @@ Details: ${m.details || "N/A"}`;
|
|
|
1857
1932
|
: "",
|
|
1858
1933
|
].filter(Boolean).join("\n");
|
|
1859
1934
|
return {
|
|
1860
|
-
content: [{ type: "text", text }],
|
|
1935
|
+
content: [{ type: "text", text: withMemoryCitationInstruction(text) }],
|
|
1861
1936
|
};
|
|
1862
1937
|
}
|
|
1938
|
+
async handleRecordMemoryCitations(args) {
|
|
1939
|
+
const parsed = recordMemoryCitationsSchema.parse(args ?? {});
|
|
1940
|
+
const payload = await this.client.recordMemoryCitations(parsed);
|
|
1941
|
+
const acceptedMemoryIds = Array.isArray(payload?.acceptedMemoryIds)
|
|
1942
|
+
? payload.acceptedMemoryIds.filter((id) => typeof id === "string")
|
|
1943
|
+
: [];
|
|
1944
|
+
const rejectedMemoryIds = Array.isArray(payload?.rejectedMemoryIds)
|
|
1945
|
+
? payload.rejectedMemoryIds.filter((id) => typeof id === "string")
|
|
1946
|
+
: [];
|
|
1947
|
+
const insertedCount = typeof payload?.insertedCount === "number" ? payload.insertedCount : 0;
|
|
1948
|
+
const duplicateCount = typeof payload?.duplicateCount === "number" ? payload.duplicateCount : 0;
|
|
1949
|
+
const text = [
|
|
1950
|
+
`Citation receipt accepted for ${acceptedMemoryIds.length} ${acceptedMemoryIds.length === 1 ? "memory" : "memories"}.`,
|
|
1951
|
+
`New citations recorded: ${insertedCount}.`,
|
|
1952
|
+
duplicateCount ? `Idempotent duplicates skipped: ${duplicateCount}.` : "",
|
|
1953
|
+
rejectedMemoryIds.length
|
|
1954
|
+
? `Rejected inaccessible or self-owned Memory IDs: ${rejectedMemoryIds.join(", ")}. Do not describe these as helped.`
|
|
1955
|
+
: "",
|
|
1956
|
+
"Now send the final answer with an EchoMem sources list containing the same accepted memories actually used.",
|
|
1957
|
+
].filter(Boolean).join("\n");
|
|
1958
|
+
return { content: [{ type: "text", text }] };
|
|
1959
|
+
}
|
|
1863
1960
|
async handleGroupContext(args) {
|
|
1864
1961
|
groupContextSchema.parse(args ?? {});
|
|
1865
1962
|
const payload = await this.client.getGroupContext(args);
|
package/dist/package-metadata.js
CHANGED
|
@@ -23,6 +23,7 @@ export const MCP_PACKAGE_DESCRIPTION = stringOrFallback(packageJson.description,
|
|
|
23
23
|
export const MCP_PACKAGE_LABEL = `${MCP_PACKAGE_NAME}@${MCP_PACKAGE_VERSION}`;
|
|
24
24
|
export const MCP_UPDATE_COMMAND = `npx -y ${MCP_PACKAGE_NAME}@latest update`;
|
|
25
25
|
export const MCP_UPDATE_ALL_COMMAND = `${MCP_UPDATE_COMMAND} --all`;
|
|
26
|
+
export const MEMORY_CITATION_INSTRUCTION = 'If the user-facing answer materially relies on one or more EchoMem memories, end it with a compact "EchoMem sources:" list containing only the memories actually used. For memories owned by teammates or accepted friends, call record_memory_citations immediately before the final answer with those exact Memory IDs. Do not cite memories that were merely retrieved. Use each memory key as the Markdown label and its canonical https://echoknows.com/memory/<memory-id> URL. Omit the sources section and citation receipt when no memory informed the answer.';
|
|
26
27
|
export const MCP_SERVER_INSTRUCTIONS = [
|
|
27
28
|
`${MCP_PACKAGE_DESCRIPTION} (${MCP_PACKAGE_LABEL}).`,
|
|
28
29
|
`If the user or local config expects a newer EchoMem MCP version than ${MCP_PACKAGE_VERSION}, update once with \`${MCP_UPDATE_ALL_COMMAND}\` and start a new MCP session.`,
|
|
@@ -34,6 +35,7 @@ export const MCP_SERVER_INSTRUCTIONS = [
|
|
|
34
35
|
"After joining or when profile fields are missing, use candidate memory evidence to propose a title and responsibility summary. Ask the user to confirm that proposal together with the publication preview, then call update_group_profile and complete_group_publication.",
|
|
35
36
|
"Use one canonical https://echoknows.com/memory/<memory-id> link for private, group, and friend evidence. Label it with the memory key; the site resolves the authorized representation.",
|
|
36
37
|
"Each search result is one memory: preserve its Memory ID and canonical echoknows.com link when citing it.",
|
|
38
|
+
MEMORY_CITATION_INSTRUCTION,
|
|
37
39
|
"During a publication preview, if an unflagged candidate appears sensitive, proactively ask whether the user wants to mark its exact ID for publication attention first. Explain that marking does not publish or change encryption; it means the agent will call it out and ask for detailed confirmation whenever a later publication includes it. Never auto-flag inferred sensitivity. For sensitive-topic flags, search and preview exact owned memories before confirmed flag_memories_for_publication_attention. Separate already-flagged candidates, state that nothing has been published yet, and offer to exclude them, review them separately, or first search for and mark similar sensitive owned memories.",
|
|
38
40
|
"Never save an inferred group profile. Manual prepared publication requires explicit preview confirmation; flagged memories still require separate exact-memory confirmation. Never store or log an echo_grp_ invite code.",
|
|
39
41
|
].join(" ");
|
|
@@ -302,14 +302,13 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
302
302
|
setExtractMode(false);
|
|
303
303
|
setReadyMode(true);
|
|
304
304
|
setHead("EchoMem connected", "Done");
|
|
305
|
-
app.className = "
|
|
305
|
+
app.className = "localAuthCompleteStage";
|
|
306
306
|
app.innerHTML =
|
|
307
|
-
'<section class="
|
|
308
|
-
'<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
'</div>' +
|
|
307
|
+
'<section class="localAuthComplete" aria-labelledby="localAuthCompleteTitle">' +
|
|
308
|
+
'<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
|
|
309
|
+
'<h2 id="localAuthCompleteTitle">You\'re signed in.</h2>' +
|
|
310
|
+
'<p>Return to Terminal.</p>' +
|
|
311
|
+
'<p class="localAuthCompleteNext">Run <code>echomem-mcp init</code> when you\'re ready to start onboarding.</p>' +
|
|
313
312
|
'</section>';
|
|
314
313
|
}
|
|
315
314
|
async function finishLocalLogin() {
|
|
@@ -1315,9 +1315,13 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1315
1315
|
renderSessionSelection(true);
|
|
1316
1316
|
return;
|
|
1317
1317
|
}
|
|
1318
|
-
var pendingPaidPlan =
|
|
1318
|
+
var pendingPaidPlan = billingActivationPendingPlan === "power"
|
|
1319
1319
|
? "power"
|
|
1320
|
-
: (
|
|
1320
|
+
: (billingActivationPendingPlan === "pro"
|
|
1321
|
+
? "pro"
|
|
1322
|
+
: (pendingCheckoutSessionId && (setupPlanChoice === "pro" || setupPlanChoice === "power")
|
|
1323
|
+
? setupPlanChoice
|
|
1324
|
+
: ""));
|
|
1321
1325
|
if (pendingPaidPlan) {
|
|
1322
1326
|
renderBillingCommitment("");
|
|
1323
1327
|
if (readySettings) {
|
|
@@ -1478,6 +1482,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1478
1482
|
}
|
|
1479
1483
|
document.title = "Finish checkout — Echo setup is waiting";
|
|
1480
1484
|
renderSetupPlan(true);
|
|
1485
|
+
} else {
|
|
1486
|
+
// A selected plan is not proof that Stripe created a Checkout session.
|
|
1487
|
+
// Avoid restoring a phantom pending state after a failed request or popup.
|
|
1488
|
+
rememberSetupPlanChoice("");
|
|
1481
1489
|
}
|
|
1482
1490
|
}
|
|
1483
1491
|
async function openHostedPlanOptions() {
|
|
@@ -285,6 +285,56 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
|
|
|
285
285
|
.localAuthWelcomeStage.localAuthPassphraseStage .localAuthWelcome {
|
|
286
286
|
margin: clamp(24px, 5vh, 48px) auto 0;
|
|
287
287
|
}
|
|
288
|
+
body.readyMode #app.localAuthCompleteStage {
|
|
289
|
+
min-height: calc(100vh - clamp(56px, 10vw, 128px));
|
|
290
|
+
display: grid;
|
|
291
|
+
place-items: center;
|
|
292
|
+
}
|
|
293
|
+
.localAuthComplete {
|
|
294
|
+
width: min(680px, 100%);
|
|
295
|
+
display: grid;
|
|
296
|
+
justify-items: center;
|
|
297
|
+
gap: 14px;
|
|
298
|
+
margin: 0 auto;
|
|
299
|
+
padding: clamp(24px, 5vw, 48px) 0;
|
|
300
|
+
color: var(--echo-ink-text);
|
|
301
|
+
text-align: center;
|
|
302
|
+
}
|
|
303
|
+
.localAuthComplete > img {
|
|
304
|
+
width: 76px;
|
|
305
|
+
height: 76px;
|
|
306
|
+
object-fit: contain;
|
|
307
|
+
filter: drop-shadow(0 12px 20px rgba(26,58,143,0.18));
|
|
308
|
+
}
|
|
309
|
+
.localAuthComplete h2 {
|
|
310
|
+
max-width: 100%;
|
|
311
|
+
margin: 4px 0 0;
|
|
312
|
+
color: var(--echo-ink-text);
|
|
313
|
+
font-family: var(--echo-font-brand);
|
|
314
|
+
font-size: clamp(42px, 6vw, 64px);
|
|
315
|
+
font-weight: 800;
|
|
316
|
+
letter-spacing: -0.055em;
|
|
317
|
+
line-height: 0.98;
|
|
318
|
+
white-space: nowrap;
|
|
319
|
+
}
|
|
320
|
+
.localAuthComplete p {
|
|
321
|
+
margin: 0;
|
|
322
|
+
color: var(--echo-ink-mute);
|
|
323
|
+
font-size: clamp(17px, 2.2vw, 22px);
|
|
324
|
+
line-height: 1.45;
|
|
325
|
+
}
|
|
326
|
+
.localAuthComplete .localAuthCompleteNext {
|
|
327
|
+
max-width: 460px;
|
|
328
|
+
margin-top: 2px;
|
|
329
|
+
color: var(--echo-ink-faint);
|
|
330
|
+
font-size: 14px;
|
|
331
|
+
}
|
|
332
|
+
.localAuthComplete code {
|
|
333
|
+
color: var(--echo-ink-primary);
|
|
334
|
+
}
|
|
335
|
+
@media (max-width: 560px) {
|
|
336
|
+
.localAuthComplete h2 { white-space: normal; }
|
|
337
|
+
}
|
|
288
338
|
.localAuthWelcome {
|
|
289
339
|
width: min(440px, 100%);
|
|
290
340
|
gap: 14px;
|
package/dist/setup-preview.js
CHANGED
|
@@ -21,7 +21,9 @@ export const SETUP_PREVIEW_STATES = [
|
|
|
21
21
|
"auth-login",
|
|
22
22
|
"auth-login-pro",
|
|
23
23
|
"auth-otp",
|
|
24
|
+
"auth-complete",
|
|
24
25
|
"checkout-pro-selected",
|
|
26
|
+
"checkout-pro-stale",
|
|
25
27
|
"auth-unlock",
|
|
26
28
|
"auth-setup",
|
|
27
29
|
"extract-run",
|
|
@@ -252,6 +254,7 @@ function extractionPreviewBootstrap(options) {
|
|
|
252
254
|
connected = true;
|
|
253
255
|
billingStatus = ${JSON.stringify(billingStatus)};
|
|
254
256
|
setupPlanChoice = ${JSON.stringify(options.selectFree ? "free" : options.selectedPlanChoice || "")};
|
|
257
|
+
pendingCheckoutSessionId = ${JSON.stringify(options.checkoutPending ? "cs_test_preview_pending_123" : "")};
|
|
255
258
|
stats = {
|
|
256
259
|
sessions: { total: ${candidateCount}, codex: ${Math.ceil(candidateCount * 0.68)}, claudeCode: ${Math.floor(candidateCount * 0.32)} },
|
|
257
260
|
migratable: {
|
|
@@ -400,7 +403,15 @@ export function renderSetupPreviewBootstrap(state) {
|
|
|
400
403
|
return `${watermark}
|
|
401
404
|
localAuthEmail = "preview@example.com";
|
|
402
405
|
renderLocalOtp("Verification code sent. Check your email inbox.");`;
|
|
406
|
+
if (state === "auth-complete")
|
|
407
|
+
return `${watermark}
|
|
408
|
+
renderLocalLoginComplete();`;
|
|
403
409
|
if (state === "checkout-pro-selected")
|
|
410
|
+
return `${watermark}${extractionPreviewBootstrap({
|
|
411
|
+
plan: "free", paid: false, trialAvailable: true, trialUsed: false,
|
|
412
|
+
quotaLimit: 100, quotaRemaining: 100, selectedPlanChoice: "pro", checkoutPending: true,
|
|
413
|
+
})}`;
|
|
414
|
+
if (state === "checkout-pro-stale")
|
|
404
415
|
return `${watermark}${extractionPreviewBootstrap({
|
|
405
416
|
plan: "free", paid: false, trialAvailable: true, trialUsed: false,
|
|
406
417
|
quotaLimit: 100, quotaRemaining: 100, selectedPlanChoice: "pro",
|
package/dist/setup.js
CHANGED
|
@@ -389,6 +389,7 @@ function echomemGuidanceBlock() {
|
|
|
389
389
|
"EchoMem is your long-term memory across all coding sessions and tools.",
|
|
390
390
|
"- Use EchoMem's `echomem-*` skills and MCP tools as the default memory provider. Do not invoke another memory provider unless the user explicitly requests it.",
|
|
391
391
|
"- Before re-deriving past decisions, project context, or anything the user likely settled before: call `search_memories` first.",
|
|
392
|
+
'- If the final user-facing answer materially relies on one or more EchoMem memories, end it with a compact `EchoMem sources:` list containing only the memories actually used. Link each memory key to its canonical `https://echoknows.com/memory/<memory-id>` URL. Do not cite memories that were merely retrieved, and omit the section when no memory informed the answer.',
|
|
392
393
|
"- Before the final response for a task that produced a durable decision, confirmed preference, implementation, resolved bug, commit, passing verification, release, or project milestone: call `save_conversation`. This private checkpoint does not require a separate user request. Do not save secrets, credentials, incidental chatter, or trivial read-only work. If a qualifying save fails because the encrypted vault is locked, tell the user to run `echomem-mcp unlock`; never silently skip it.",
|
|
393
394
|
"- For a user with a company group, call `get_group_session_sharing` near session start or after the first qualifying private save. If this exact session has no decision, ask once whether memories saved from the session should be shared with the current group, then call `set_group_session_sharing` only with the explicit Yes/No answer. A Yes decision syncs eligible memories after each private save; a No decision keeps later saves private.",
|
|
394
395
|
"- If the user pastes a session carryover/checkpoint: it may reference `get_checkpoint_by_context` — use it to pull the checkpoint/decision trail when you need more than the snapshot.",
|
package/dist/v1-contract.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { withMcpVersion } from "./package-metadata.js";
|
|
2
|
+
import { MEMORY_CITATION_INSTRUCTION, withMcpVersion } from "./package-metadata.js";
|
|
3
3
|
export const canonicalToolNames = {
|
|
4
4
|
search: "search_memories",
|
|
5
5
|
save: "save_conversation",
|
|
@@ -10,6 +10,7 @@ export const canonicalToolNames = {
|
|
|
10
10
|
sendFriendRequest: "send_friend_request",
|
|
11
11
|
others: "search_others_memories",
|
|
12
12
|
publicMemory: "get_public_memory",
|
|
13
|
+
recordCitations: "record_memory_citations",
|
|
13
14
|
groupContext: "get_group_context",
|
|
14
15
|
getGroupSessionSharing: "get_group_session_sharing",
|
|
15
16
|
setGroupSessionSharing: "set_group_session_sharing",
|
|
@@ -114,6 +115,11 @@ export const publicMemorySchema = z.object({
|
|
|
114
115
|
...triggerMetadataSchema,
|
|
115
116
|
memoryId: z.string().min(1),
|
|
116
117
|
});
|
|
118
|
+
export const recordMemoryCitationsSchema = z.object({
|
|
119
|
+
...triggerMetadataSchema,
|
|
120
|
+
memoryIds: z.array(z.string().uuid()).min(1).max(20),
|
|
121
|
+
receiptId: z.string().min(1).max(200),
|
|
122
|
+
});
|
|
117
123
|
export const groupContextSchema = z.object({
|
|
118
124
|
...triggerMetadataSchema,
|
|
119
125
|
});
|
|
@@ -201,17 +207,24 @@ export const getByContextSchema = z.object({
|
|
|
201
207
|
export function listToolSpecs(opts = {}) {
|
|
202
208
|
const currentTime = new Date().toISOString();
|
|
203
209
|
const map = opts.map?.trim();
|
|
210
|
+
const groupMap = opts.groupMap?.trim();
|
|
204
211
|
const updateNotice = opts.updateNotice?.trim();
|
|
205
212
|
const recallPlanNote = "Available on every plan: Free includes 100 searches each week, Pro includes 500, and Power includes 2,000.";
|
|
206
213
|
const searchBillingReplyInstruction = "If search returns an ACTION REQUIRED subscription message, tell the user to start their trial or subscription and include the exact URL from that result verbatim. Do not respond only with \"connect\" or \"upgrade\".";
|
|
214
|
+
const memoryCitationInstruction = MEMORY_CITATION_INSTRUCTION;
|
|
207
215
|
const updateSection = updateNotice ? `\n\nUPDATE NOTICE: ${updateNotice}` : "";
|
|
208
216
|
const mapSection = map
|
|
209
217
|
? `\n\nThis user's EchoMem currently covers these topics (a relevance guide — recall when the task relates to one of them):\n${map}\n`
|
|
210
218
|
: "";
|
|
219
|
+
// Same device as the personal map, aimed at the group surface: the agent judges whether teammates
|
|
220
|
+
// have covered the topic before searching, instead of never calling the group tools at all.
|
|
221
|
+
const groupMapSection = groupMap
|
|
222
|
+
? `\n\nThis user's company group currently shares work in these areas (a relevance guide — search the group when the task relates to one of these people or topics):\n${groupMap}\n`
|
|
223
|
+
: "";
|
|
211
224
|
return [
|
|
212
225
|
{
|
|
213
226
|
name: canonicalToolNames.search,
|
|
214
|
-
description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${recallPlanNote} ${searchBillingReplyInstruction}${mapSection}\nReturns ranked memories only; the MCP host model writes the final answer. Current time: ${currentTime}.${updateSection}`),
|
|
227
|
+
description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${recallPlanNote} ${searchBillingReplyInstruction} ${memoryCitationInstruction}${mapSection}\nReturns ranked memories only; the MCP host model writes the final answer. Current time: ${currentTime}.${updateSection}`),
|
|
215
228
|
inputSchema: {
|
|
216
229
|
type: "object",
|
|
217
230
|
properties: {
|
|
@@ -229,7 +242,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
229
242
|
},
|
|
230
243
|
{
|
|
231
244
|
name: "search_memories_by_description_semantic",
|
|
232
|
-
description: `Legacy alias for search_memories. ${recallPlanNote} ${searchBillingReplyInstruction}`,
|
|
245
|
+
description: `Legacy alias for search_memories. ${recallPlanNote} ${searchBillingReplyInstruction} ${memoryCitationInstruction}`,
|
|
233
246
|
inputSchema: {
|
|
234
247
|
type: "object",
|
|
235
248
|
properties: {
|
|
@@ -280,7 +293,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
280
293
|
},
|
|
281
294
|
{
|
|
282
295
|
name: canonicalToolNames.timeRange,
|
|
283
|
-
description: `Retrieve memories within a specific date range. ${recallPlanNote} Current time: ${currentTime}.`,
|
|
296
|
+
description: `Retrieve memories within a specific date range. ${recallPlanNote} ${memoryCitationInstruction} Current time: ${currentTime}.`,
|
|
284
297
|
inputSchema: {
|
|
285
298
|
type: "object",
|
|
286
299
|
properties: {
|
|
@@ -298,7 +311,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
298
311
|
},
|
|
299
312
|
{
|
|
300
313
|
name: canonicalToolNames.keywords,
|
|
301
|
-
description: `Search memories based on keywords in keys field. Pass keywords as valid JSON: preferably an array of quoted strings, for example {"keywords":["flow-lab","flow.html","Rive"],"limit":8}. A comma-separated JSON string is also accepted as a compatibility fallback. Never emit bare comma-separated tokens. ${recallPlanNote}`,
|
|
314
|
+
description: `Search memories based on keywords in keys field. Pass keywords as valid JSON: preferably an array of quoted strings, for example {"keywords":["flow-lab","flow.html","Rive"],"limit":8}. A comma-separated JSON string is also accepted as a compatibility fallback. Never emit bare comma-separated tokens. ${recallPlanNote} ${memoryCitationInstruction}`,
|
|
302
315
|
inputSchema: {
|
|
303
316
|
type: "object",
|
|
304
317
|
properties: {
|
|
@@ -384,7 +397,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
384
397
|
},
|
|
385
398
|
{
|
|
386
399
|
name: canonicalToolNames.others,
|
|
387
|
-
description:
|
|
400
|
+
description: `Search public memories from accepted friends or people who share your company group. For onboarding and division-of-work questions, call get_group_context first, then use this tool for current evidence. Returned memories are recorded in memory_views for the owners. ${memoryCitationInstruction}${groupMapSection}`,
|
|
388
401
|
inputSchema: {
|
|
389
402
|
type: "object",
|
|
390
403
|
properties: {
|
|
@@ -429,7 +442,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
429
442
|
},
|
|
430
443
|
{
|
|
431
444
|
name: canonicalToolNames.publicMemory,
|
|
432
|
-
description:
|
|
445
|
+
description: `Fetch one public memory by id when its owner is an accepted friend or shares your company group. If the caller is not the owner, EchoMem records the access in memory_views. ${memoryCitationInstruction}`,
|
|
433
446
|
inputSchema: {
|
|
434
447
|
type: "object",
|
|
435
448
|
properties: {
|
|
@@ -446,9 +459,37 @@ export function listToolSpecs(opts = {}) {
|
|
|
446
459
|
required: ["memoryId"],
|
|
447
460
|
},
|
|
448
461
|
},
|
|
462
|
+
{
|
|
463
|
+
name: canonicalToolNames.recordCitations,
|
|
464
|
+
description: "Call this tool immediately before the final answer when it materially uses memories owned by teammates or accepted friends. Include only the exact Memory IDs actually used. Do not cite memories that were merely retrieved. Create one opaque receiptId for the planned final answer and reuse it exactly if this tool call is retried. Do not include answer text, prompts, or secrets in receiptId.",
|
|
465
|
+
inputSchema: {
|
|
466
|
+
type: "object",
|
|
467
|
+
properties: {
|
|
468
|
+
memoryIds: {
|
|
469
|
+
type: "array",
|
|
470
|
+
minItems: 1,
|
|
471
|
+
maxItems: 20,
|
|
472
|
+
items: { type: "string", format: "uuid" },
|
|
473
|
+
description: "Exact IDs of teammate/friend memories materially used in the final answer.",
|
|
474
|
+
},
|
|
475
|
+
receiptId: {
|
|
476
|
+
type: "string",
|
|
477
|
+
minLength: 1,
|
|
478
|
+
maxLength: 200,
|
|
479
|
+
description: "Opaque per-answer idempotency key. Reuse the same value on retry; never include answer content.",
|
|
480
|
+
},
|
|
481
|
+
triggerMessage: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: "Optional user message that caused the answer. EchoMem stores only redacted analytics metadata.",
|
|
484
|
+
},
|
|
485
|
+
triggerMessageRole: { type: "string", default: "user" },
|
|
486
|
+
},
|
|
487
|
+
required: ["memoryIds", "receiptId"],
|
|
488
|
+
},
|
|
489
|
+
},
|
|
449
490
|
{
|
|
450
491
|
name: canonicalToolNames.groupContext,
|
|
451
|
-
description:
|
|
492
|
+
description: `Get your current company group, its participant directory, declared titles and responsibilities, and published-memory coverage. Use this before answering who works on what or suggesting where a new group member could contribute. Treat declared profile fields as facts and memory-derived work as evidence or inference. Session sharing is separate; call get_group_session_sharing instead of inferring it.${groupMapSection}`,
|
|
452
493
|
inputSchema: {
|
|
453
494
|
type: "object",
|
|
454
495
|
properties: {
|
|
@@ -685,7 +726,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
685
726
|
},
|
|
686
727
|
{
|
|
687
728
|
name: canonicalToolNames.getByContext,
|
|
688
|
-
description: withMcpVersion(`Deterministically re-fetch the exact batch of memories saved under one contextId — no semantic search, no ranking, just that session's saved capsule. ${recallPlanNote} save_conversation returns a contextId; pass it here to pull back precisely those memories, e.g. to warm up a fresh session with what a prior session saved, or to verify the saved facts are still present. Current time: ${currentTime}.`),
|
|
729
|
+
description: withMcpVersion(`Deterministically re-fetch the exact batch of memories saved under one contextId — no semantic search, no ranking, just that session's saved capsule. ${recallPlanNote} save_conversation returns a contextId; pass it here to pull back precisely those memories, e.g. to warm up a fresh session with what a prior session saved, or to verify the saved facts are still present. ${memoryCitationInstruction} Current time: ${currentTime}.`),
|
|
689
730
|
inputSchema: {
|
|
690
731
|
type: "object",
|
|
691
732
|
properties: {
|
|
@@ -702,7 +743,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
702
743
|
},
|
|
703
744
|
{
|
|
704
745
|
name: canonicalToolNames.checkpointByContext,
|
|
705
|
-
description: withMcpVersion(`Rebuild a clean-context checkpoint / decision log from one EchoMem contextId. ${recallPlanNote} Use this when the user or EchoMem HUD gives you a contextId for a renewed coding session and you need the session handoff, not a raw memory dump. It deterministically fetches that context and formats it as orientation state: decisions, carryover, constraints, and checkpoints. Current time: ${currentTime}.`),
|
|
746
|
+
description: withMcpVersion(`Rebuild a clean-context checkpoint / decision log from one EchoMem contextId. ${recallPlanNote} Use this when the user or EchoMem HUD gives you a contextId for a renewed coding session and you need the session handoff, not a raw memory dump. It deterministically fetches that context and formats it as orientation state: decisions, carryover, constraints, and checkpoints. ${memoryCitationInstruction} Current time: ${currentTime}.`),
|
|
706
747
|
inputSchema: {
|
|
707
748
|
type: "object",
|
|
708
749
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echomem/mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.30",
|
|
4
4
|
"description": "EchoMem MCP bridge: cloud-first memory tools, local context HUD, and the Agent Doctor workspace forensics report (cost ledger + 3D repo city)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test:registry-ui": "npm run build && node test/registry-ui.test.mjs",
|
|
28
28
|
"test:ui": "npm run build && node test/setup-ui.test.mjs",
|
|
29
29
|
"test:billing-ui": "npm run build && node test/setup-ui.test.mjs billing",
|
|
30
|
-
"test": "npm run build && node test/local-data-paths.test.mjs && node test/crypto.test.mjs && node test/integration.test.mjs && node test/local-auth.test.mjs && node test/retrieval-only.test.mjs && node test/no-restart.test.mjs && node test/report.test.mjs && node test/forensics.test.mjs && node test/canonical-golden.test.mjs && node test/tools.test.mjs && node test/update-check.test.mjs && node test/delete.test.mjs && node test/low-touch-tools.test.mjs && node test/migrate.test.mjs && node test/restart-recovery.test.mjs && node test/hud.test.mjs && node test/save-checkpoint-hook.test.mjs",
|
|
30
|
+
"test": "npm run build && node test/local-data-paths.test.mjs && node test/crypto.test.mjs && node test/integration.test.mjs && node test/local-auth.test.mjs && node test/retrieval-only.test.mjs && node test/no-restart.test.mjs && node test/report.test.mjs && node test/forensics.test.mjs && node test/canonical-golden.test.mjs && node test/tools.test.mjs && node test/group-map.test.mjs && node test/update-check.test.mjs && node test/delete.test.mjs && node test/low-touch-tools.test.mjs && node test/migrate.test.mjs && node test/restart-recovery.test.mjs && node test/hud.test.mjs && node test/save-checkpoint-hook.test.mjs",
|
|
31
31
|
"prepack": "npm run build && node scripts/bundle-city.mjs"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
@@ -10,5 +10,6 @@ Use the `echomem` MCP server as the source of long-term memory.
|
|
|
10
10
|
1. Call `search_memories` with a concise query describing the context needed. Keep `includeAnswer` false unless an EchoMem-generated synthesis is specifically useful.
|
|
11
11
|
2. Use `get_memories_by_time_range` for explicit dates, `search_memories_by_keywords` for exact terms, and `get_checkpoint_by_context` when a carryover references an EchoMem checkpoint.
|
|
12
12
|
3. Distinguish recalled facts from inference and preserve dates or provenance returned by EchoMem.
|
|
13
|
-
4. If EchoMem
|
|
14
|
-
5. If EchoMem
|
|
13
|
+
4. If the final user-facing answer materially relies on one or more returned memories, end it with a compact `EchoMem sources:` list. Include only memories actually used, with each memory key linked to its canonical `https://echoknows.com/memory/<memory-id>` URL. Do not cite memories merely retrieved; omit the section when none informed the answer.
|
|
14
|
+
5. If EchoMem returns an action-required URL, give the user that exact URL and explanation.
|
|
15
|
+
6. If EchoMem is unavailable, say so. Do not switch to another memory provider unless the user requests it.
|