@echomem/mcp 1.4.38 → 1.4.40
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 +11 -62
- package/dist/package-metadata.js +1 -1
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-extraction.js +9 -3
- package/dist/setup-page/styles-mvp.js +46 -0
- package/dist/setup.js +25 -9
- package/dist/v1-contract.js +5 -5
- package/package.json +1 -1
- package/templates/codex-skills/echomem-save/SKILL.md +2 -3
- package/templates/echomem-recall.md +2 -3
package/dist/index.js
CHANGED
|
@@ -1719,7 +1719,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1719
1719
|
syncedGroupNames.length
|
|
1720
1720
|
? `Saved privately first and synced to approved groups: ${syncedGroupNames.join(", ")}.`
|
|
1721
1721
|
: "Saved to your private memory.",
|
|
1722
|
-
`This user has multiple groups: ${availableGroups.map((group) => `${readString(group, "name") ?? "Unnamed group"} (${readString(group, "id") ?? "unknown id"})`).join(", ")}. Call request_group_session_sharing with this conversation scope and one groupId at a time
|
|
1722
|
+
`This user has multiple groups: ${availableGroups.map((group) => `${readString(group, "name") ?? "Unnamed group"} (${readString(group, "id") ?? "unknown id"})`).join(", ")}. Call request_group_session_sharing with this conversation scope and one groupId at a time to obtain a separate text confirmation question for each group.`,
|
|
1723
1723
|
failedSyncs.length
|
|
1724
1724
|
? `${failedSyncs.length} approved group sync(s) failed; private memories were preserved.`
|
|
1725
1725
|
: "",
|
|
@@ -1728,7 +1728,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1728
1728
|
: "",
|
|
1729
1729
|
].filter(Boolean).join(" ")
|
|
1730
1730
|
: sharing?.decision === null || sharing?.decision === undefined
|
|
1731
|
-
? `Saved to your private memory. Call request_group_session_sharing with this conversation scope${readString(sharingGroup, "id") ? ` and groupId ${readString(sharingGroup, "id")}` : ""}
|
|
1731
|
+
? `Saved to your private memory. Call request_group_session_sharing with this conversation scope${readString(sharingGroup, "id") ? ` and groupId ${readString(sharingGroup, "id")}` : ""}, relay its exact text question for ${readString(sharingGroup, "name") ?? "your current group"}, and call set_group_session_sharing only after an explicit Yes or No. Silence leaves the state unset.`
|
|
1732
1732
|
: sharing.decision === "private"
|
|
1733
1733
|
? "Saved to your private memory."
|
|
1734
1734
|
: syncedGroupNames.length
|
|
@@ -2202,7 +2202,7 @@ Details: ${m.details || "N/A"}`;
|
|
|
2202
2202
|
return {
|
|
2203
2203
|
content: [{
|
|
2204
2204
|
type: "text",
|
|
2205
|
-
text: `${scopeText}\n\nThis user belongs to multiple groups: ${choices}. Reuse this same conversation scope and call request_group_session_sharing once per groupId
|
|
2205
|
+
text: `${scopeText}\n\nThis user belongs to multiple groups: ${choices}. Reuse this same conversation scope and call request_group_session_sharing once per groupId to obtain a separate text confirmation question for each group.`,
|
|
2206
2206
|
}],
|
|
2207
2207
|
};
|
|
2208
2208
|
}
|
|
@@ -2212,7 +2212,7 @@ Details: ${m.details || "N/A"}`;
|
|
|
2212
2212
|
return {
|
|
2213
2213
|
content: [{
|
|
2214
2214
|
type: "text",
|
|
2215
|
-
text: `${scopeText}\n\nNo sharing decision exists for this conversation. Call request_group_session_sharing with the scope above${readString(group, "id") ? ` and groupId ${readString(group, "id")}` : ""}
|
|
2215
|
+
text: `${scopeText}\n\nNo sharing decision exists for this conversation. Call request_group_session_sharing with the scope above${readString(group, "id") ? ` and groupId ${readString(group, "id")}` : ""}, relay its exact text question, and call set_group_session_sharing only after an explicit Yes or No. Silence leaves the state unset.`,
|
|
2216
2216
|
}],
|
|
2217
2217
|
};
|
|
2218
2218
|
}
|
|
@@ -2233,10 +2233,10 @@ Details: ${m.details || "N/A"}`;
|
|
|
2233
2233
|
? `Conversation sharing scope: ${groupSharingScopeId}\nReuse this exact groupSharingScopeId only in this conversation for later get/request/set/save calls. Never persist it as memory.`
|
|
2234
2234
|
: "";
|
|
2235
2235
|
}
|
|
2236
|
-
|
|
2236
|
+
sharingPromptText(scopeText, groupName) {
|
|
2237
2237
|
return [
|
|
2238
2238
|
scopeText,
|
|
2239
|
-
`
|
|
2239
|
+
`Ask exactly: “Share eligible memories saved from this conversation with ${groupName}?” Only an explicit Yes or No may be passed to set_group_session_sharing. Silence leaves the state unset.`,
|
|
2240
2240
|
].filter(Boolean).join("\n\n");
|
|
2241
2241
|
}
|
|
2242
2242
|
formatGroupSessionSharingUpdate(share, payload, scopeText = "") {
|
|
@@ -2310,60 +2310,9 @@ Details: ${m.details || "N/A"}`;
|
|
|
2310
2310
|
}],
|
|
2311
2311
|
};
|
|
2312
2312
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
content: [{ type: "text", text: this.sharingFallbackText(scopeText, groupName) }],
|
|
2317
|
-
};
|
|
2318
|
-
}
|
|
2319
|
-
let elicitation;
|
|
2320
|
-
try {
|
|
2321
|
-
elicitation = await this.server.elicitInput({
|
|
2322
|
-
mode: "form",
|
|
2323
|
-
message: `EchoMem saved this checkpoint privately. Choose whether eligible memories from this conversation should also be shared with ${groupName}. Flagged memories always remain private.`,
|
|
2324
|
-
requestedSchema: {
|
|
2325
|
-
type: "object",
|
|
2326
|
-
properties: {
|
|
2327
|
-
sharingDecision: {
|
|
2328
|
-
type: "string",
|
|
2329
|
-
title: "Team sharing",
|
|
2330
|
-
description: `Choose whether eligible conversation memories may be shared with ${groupName}.`,
|
|
2331
|
-
oneOf: [
|
|
2332
|
-
{ const: "share", title: "Share with team" },
|
|
2333
|
-
{ const: "private", title: "Keep private" },
|
|
2334
|
-
],
|
|
2335
|
-
},
|
|
2336
|
-
},
|
|
2337
|
-
required: ["sharingDecision"],
|
|
2338
|
-
},
|
|
2339
|
-
});
|
|
2340
|
-
}
|
|
2341
|
-
catch {
|
|
2342
|
-
return {
|
|
2343
|
-
content: [{ type: "text", text: this.sharingFallbackText(scopeText, groupName) }],
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2346
|
-
if (elicitation.action !== "accept") {
|
|
2347
|
-
return {
|
|
2348
|
-
content: [{
|
|
2349
|
-
type: "text",
|
|
2350
|
-
text: this.sharingFallbackText(scopeText, groupName),
|
|
2351
|
-
}],
|
|
2352
|
-
};
|
|
2353
|
-
}
|
|
2354
|
-
const sharingDecision = readString(elicitation.content ?? {}, "sharingDecision");
|
|
2355
|
-
if (sharingDecision !== "share" && sharingDecision !== "private") {
|
|
2356
|
-
return {
|
|
2357
|
-
content: [{ type: "text", text: this.sharingFallbackText(scopeText, groupName) }],
|
|
2358
|
-
};
|
|
2359
|
-
}
|
|
2360
|
-
const updatePayload = await this.client.setGroupSessionSharing({
|
|
2361
|
-
...parsed,
|
|
2362
|
-
groupSharingScopeId,
|
|
2363
|
-
share: sharingDecision === "share",
|
|
2364
|
-
confirmed: true,
|
|
2365
|
-
});
|
|
2366
|
-
return this.formatGroupSessionSharingUpdate(sharingDecision === "share", updatePayload, scopeText);
|
|
2313
|
+
return {
|
|
2314
|
+
content: [{ type: "text", text: this.sharingPromptText(scopeText, groupName) }],
|
|
2315
|
+
};
|
|
2367
2316
|
}
|
|
2368
2317
|
async handleSetGroupSessionSharing(args) {
|
|
2369
2318
|
const parsed = setGroupSessionSharingSchema.parse(args ?? {});
|
|
@@ -2418,8 +2367,8 @@ Details: ${m.details || "N/A"}`;
|
|
|
2418
2367
|
content: [{
|
|
2419
2368
|
type: "text",
|
|
2420
2369
|
text: payload?.alreadyMember
|
|
2421
|
-
? `You are already a member of ${payload?.group?.name ?? "this group"}. No memories were published. Use prepare_group_publication to review memories and propose any missing title or responsibility fields, then call request_group_session_sharing
|
|
2422
|
-
: `Joined ${payload?.group?.name ?? "the company group"}. No memories were published. Next use prepare_group_publication to review candidates, infer a proposed title and responsibility summary, and ask the user to confirm that profile together with the publication preview. Also call request_group_session_sharing without a scope
|
|
2370
|
+
? `You are already a member of ${payload?.group?.name ?? "this group"}. No memories were published. Use prepare_group_publication to review memories and propose any missing title or responsibility fields, then call request_group_session_sharing and relay its exact text question. Silence stays unset for a later checkpoint.`
|
|
2371
|
+
: `Joined ${payload?.group?.name ?? "the company group"}. No memories were published. Next use prepare_group_publication to review candidates, infer a proposed title and responsibility summary, and ask the user to confirm that profile together with the publication preview. Also call request_group_session_sharing without a scope and relay its exact text question. Silence leaves consent unset for a later checkpoint.`,
|
|
2423
2372
|
}],
|
|
2424
2373
|
};
|
|
2425
2374
|
}
|
package/dist/package-metadata.js
CHANGED
|
@@ -38,7 +38,7 @@ export const MCP_SERVER_INSTRUCTIONS = [
|
|
|
38
38
|
"Before re-deriving prior decisions or preferences, use search_memories.",
|
|
39
39
|
`Before the final response for a durable decision, implementation, fix, commit, passing verification, release, or milestone, call save_conversation. Skip secrets and trivial work. If the encrypted vault is locked, tell the user to ${MCP_VAULT_UNLOCK_INSTRUCTION}.`,
|
|
40
40
|
"After a successful save, show every memory created by that call in a compact EchoMem saved: list with canonical links; this is separate from \"EchoMem sources:\".",
|
|
41
|
-
"For company groups, call request_group_session_sharing near conversation start or after a qualifying save
|
|
41
|
+
"For company groups, call request_group_session_sharing near conversation start or after a qualifying save, relay its exact text question, and call set_group_session_sharing only after an explicit Yes or No. Omit groupSharingScopeId only on the first call, then reuse the returned scope only in this conversation. Each group needs an explicit choice; silence stays unset. Never infer consent. Flagged memories stay private.",
|
|
42
42
|
"EchoMem credential identity is authoritative over Claude profiles, host accounts, git identity, or inference. Use get_group_context before group-orientation answers and never re-filter owners returned by search_others_memories.",
|
|
43
43
|
"When a final answer materially uses teammate or friend memories, call record_memory_citations with only the exact used Memory IDs and a unique per-answer receiptId.",
|
|
44
44
|
"End memory-informed answers with a compact EchoMem sources: list. Use each memory key as the label and https://echoknows.com/memory/<memory-id> as its canonical link.",
|
|
@@ -8,7 +8,7 @@ const CHECKPOINT_REASON = [
|
|
|
8
8
|
"Review the work that just completed. If it produced a durable decision, confirmed preference, implementation, resolved bug, commit, passing verification, release, or project milestone, call `save_conversation` now with a concise structured summary of the outcome, reasoning, relevant files, and constraints.",
|
|
9
9
|
"Do not save secrets or incidental chatter. If the work was not durable, do not call the tool and finish normally.",
|
|
10
10
|
"Private persistence happens first. For a user with a company group, call `request_group_session_sharing` near conversation start or after a qualifying private save.",
|
|
11
|
-
"On the first request_group_session_sharing call in a conversation, omit groupSharingScopeId so EchoMem mints a fresh opaque scope. Reuse that exact scope only in this conversation for later get/request/set/save calls, and never persist it as memory. If multiple groups are returned, reuse the same scope and call once per groupId.
|
|
11
|
+
"On the first request_group_session_sharing call in a conversation, omit groupSharingScopeId so EchoMem mints a fresh opaque scope. Reuse that exact scope only in this conversation for later get/request/set/save calls, and never persist it as memory. If multiple groups are returned, reuse the same scope and call once per groupId. Relay the tool's exact text question and call set_group_session_sharing only after an explicit Yes/No. Silence leaves that group's state unset. A Share decision syncs eligible memories from later saves carrying the same scope to that group; saves automatically sync to every approved group. Keep private keeps them private for that group.",
|
|
12
12
|
"Flagged memories are withheld from automatic conversation sync and remain private.",
|
|
13
13
|
"If EchoMem reports that the encrypted vault is locked, tell the user to open Echo Desktop and unlock the vault there; on a headless system, use `echomem-mcp unlock`. Never silently skip a qualifying checkpoint.",
|
|
14
14
|
].join(" ");
|
|
@@ -1285,10 +1285,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1285
1285
|
var slot = document.getElementById("setupPlan");
|
|
1286
1286
|
if (!slot) return;
|
|
1287
1287
|
var readySettings = slot.closest ? slot.closest(".readySettings") : null;
|
|
1288
|
+
var planGateDecision = slot.closest ? slot.closest(".planGateDecision") : null;
|
|
1288
1289
|
if (!canExtract) {
|
|
1289
1290
|
slot.innerHTML = "";
|
|
1290
1291
|
slot.classList.add("is-hidden");
|
|
1291
1292
|
if (readySettings) readySettings.classList.remove("is-plan-open", "is-checkout-pending");
|
|
1293
|
+
if (planGateDecision) planGateDecision.classList.remove("is-checkout-pending");
|
|
1292
1294
|
renderBillingCommitment("");
|
|
1293
1295
|
return;
|
|
1294
1296
|
}
|
|
@@ -1303,6 +1305,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1303
1305
|
return;
|
|
1304
1306
|
}
|
|
1305
1307
|
if (readySettings) readySettings.classList.remove("is-plan-open", "is-checkout-pending");
|
|
1308
|
+
if (planGateDecision) planGateDecision.classList.remove("is-checkout-pending");
|
|
1306
1309
|
rememberSetupPlanChoice("");
|
|
1307
1310
|
setupPlanChoice = "paid";
|
|
1308
1311
|
var planLabel = plan.charAt(0).toUpperCase() + plan.slice(1);
|
|
@@ -1329,6 +1332,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1329
1332
|
}
|
|
1330
1333
|
if (setupPlanChoice === "free") {
|
|
1331
1334
|
if (readySettings) readySettings.classList.remove("is-plan-open", "is-checkout-pending");
|
|
1335
|
+
if (planGateDecision) planGateDecision.classList.remove("is-checkout-pending");
|
|
1332
1336
|
slot.innerHTML = '<div class="setupPlanConfirmed"><span class="setupPlanConfirmedIcon" aria-hidden="true">' + setupIcon("check") + '</span><span class="setupPlanConfirmedCopy"><strong>Original Echo</strong><span>100 coding sessions and 100 memory recalls each week.</span></span><button type="button" class="textButton" id="changeSetupPlan">Change</button></div>';
|
|
1333
1337
|
renderBillingCommitment("");
|
|
1334
1338
|
var changeBtn = document.getElementById("changeSetupPlan");
|
|
@@ -1352,6 +1356,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1352
1356
|
readySettings.classList.remove("is-plan-open");
|
|
1353
1357
|
readySettings.classList.add("is-checkout-pending");
|
|
1354
1358
|
}
|
|
1359
|
+
if (planGateDecision) planGateDecision.classList.add("is-checkout-pending");
|
|
1355
1360
|
var selectedPaidPlan = pendingPaidPlan === "power" ? "Power" : "Pro";
|
|
1356
1361
|
var pendingHasTrial = !billingStatus || billingStatus.trialAvailable !== false;
|
|
1357
1362
|
var pendingPrice = pendingPaidPlan === "power" ? "$100" : "$20";
|
|
@@ -1361,15 +1366,15 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1361
1366
|
: "Checking automatically every few seconds");
|
|
1362
1367
|
slot.innerHTML =
|
|
1363
1368
|
'<section class="setupPlanChoice setupPlanCheckout" aria-labelledby="setupPlanHeading">' +
|
|
1364
|
-
'<div class="setupPlanIntro"><h3 id="setupPlanHeading">' + esc(pendingHasTrial ? selectedPaidPlan + " trial — $0 today" : selectedPaidPlan + " subscription") + '</h3><p>
|
|
1369
|
+
'<div class="setupPlanIntro"><h3 id="setupPlanHeading">' + esc(pendingHasTrial ? selectedPaidPlan + " trial — $0 today" : selectedPaidPlan + " subscription") + '</h3><p>Complete checkout in Stripe, then return here. This page updates automatically.</p></div>' +
|
|
1365
1370
|
'<dl class="checkoutCommitment">' +
|
|
1366
1371
|
'<div><dt>Today</dt><dd>' + esc(pendingHasTrial ? "$0" : pendingPrice) + '</dd></div>' +
|
|
1367
1372
|
'<div><dt>History import</dt><dd>' + esc(pendingLimit) + ' sessions</dd></div>' +
|
|
1368
1373
|
'<div><dt>' + esc(pendingHasTrial ? "First charge" : "Billing") + '</dt><dd>' + esc(pendingHasTrial ? pendingPrice + " on " + trialChargeDateText() : pendingPrice + "/month now") + '</dd></div>' +
|
|
1369
1374
|
'</dl>' +
|
|
1370
|
-
'<div class="checkoutActions"><button type="button" class="primary" id="continueSelectedPaidPlan">Reopen Stripe checkout</button
|
|
1375
|
+
'<div class="checkoutActions"><button type="button" class="primary" id="continueSelectedPaidPlan">Reopen Stripe checkout</button></div>' +
|
|
1371
1376
|
'<p class="checkoutPollStatus" id="setupPlanStatus" role="status" aria-live="polite">' + esc(pendingStatus) + '</p>' +
|
|
1372
|
-
'<div class="setupPlanFoot"><button type="button" class="textButton" id="changeSelectedPaidPlan">Choose another plan</button></div>' +
|
|
1377
|
+
'<div class="setupPlanFoot"><button type="button" class="textButton" id="checkPaymentStatus">Check payment status</button><button type="button" class="textButton" id="changeSelectedPaidPlan">Choose another plan</button></div>' +
|
|
1373
1378
|
'</section>';
|
|
1374
1379
|
var checkPaid = document.getElementById("checkPaymentStatus");
|
|
1375
1380
|
if (checkPaid) checkPaid.onclick = function () {
|
|
@@ -1395,6 +1400,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1395
1400
|
readySettings.classList.remove("is-checkout-pending");
|
|
1396
1401
|
readySettings.classList.add("is-plan-open");
|
|
1397
1402
|
}
|
|
1403
|
+
if (planGateDecision) planGateDecision.classList.remove("is-checkout-pending");
|
|
1398
1404
|
slot.innerHTML = renderPlanHabitat();
|
|
1399
1405
|
renderBillingCommitment("");
|
|
1400
1406
|
bindPlanHabitat();
|
|
@@ -667,6 +667,52 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
|
|
|
667
667
|
color: var(--echo-ink-text);
|
|
668
668
|
text-align: left;
|
|
669
669
|
}
|
|
670
|
+
.planGateDecision.is-checkout-pending {
|
|
671
|
+
width: min(680px, 100%);
|
|
672
|
+
}
|
|
673
|
+
.planGateDecision.is-checkout-pending .setupPlanSlot {
|
|
674
|
+
margin-top: 8px;
|
|
675
|
+
}
|
|
676
|
+
.planGateDecision.is-checkout-pending .setupPlanCheckout {
|
|
677
|
+
display: grid;
|
|
678
|
+
gap: 16px;
|
|
679
|
+
margin: 0 auto;
|
|
680
|
+
overflow: visible;
|
|
681
|
+
border: 1px solid rgba(26,58,143,0.11);
|
|
682
|
+
border-radius: 20px;
|
|
683
|
+
background: rgba(255,255,255,0.78);
|
|
684
|
+
padding: clamp(22px, 4vw, 34px);
|
|
685
|
+
box-shadow: 0 22px 55px -42px rgba(20,36,84,0.5);
|
|
686
|
+
backdrop-filter: blur(10px);
|
|
687
|
+
}
|
|
688
|
+
.planGateDecision.is-checkout-pending .setupPlanIntro {
|
|
689
|
+
padding: 0;
|
|
690
|
+
}
|
|
691
|
+
.planGateDecision.is-checkout-pending .setupPlanIntro h3 {
|
|
692
|
+
font-size: clamp(25px, 3vw, 32px);
|
|
693
|
+
line-height: 1.2;
|
|
694
|
+
}
|
|
695
|
+
.planGateDecision.is-checkout-pending .setupPlanIntro p {
|
|
696
|
+
max-width: 620px;
|
|
697
|
+
margin-top: 7px;
|
|
698
|
+
font-size: 14px;
|
|
699
|
+
line-height: 1.5;
|
|
700
|
+
}
|
|
701
|
+
.planGateDecision.is-checkout-pending .checkoutActions {
|
|
702
|
+
grid-template-columns: 1fr;
|
|
703
|
+
}
|
|
704
|
+
.planGateDecision.is-checkout-pending .setupPlanFoot {
|
|
705
|
+
justify-content: flex-start;
|
|
706
|
+
gap: 18px;
|
|
707
|
+
margin-top: 0;
|
|
708
|
+
}
|
|
709
|
+
.planGateDecision.is-checkout-pending .setupPlanFoot .textButton {
|
|
710
|
+
min-height: 0;
|
|
711
|
+
padding: 2px 0;
|
|
712
|
+
color: var(--echo-ink-mute);
|
|
713
|
+
font-size: 11px;
|
|
714
|
+
font-weight: 700;
|
|
715
|
+
}
|
|
670
716
|
.mvpPricing {
|
|
671
717
|
width: 100%;
|
|
672
718
|
}
|
package/dist/setup.js
CHANGED
|
@@ -360,7 +360,7 @@ function echomemGuidanceBlock() {
|
|
|
360
360
|
'- 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.',
|
|
361
361
|
"- 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 open Echo Desktop and unlock the vault there; on a headless system, use `echomem-mcp unlock`. Never silently skip it.",
|
|
362
362
|
'- After `save_conversation` succeeds and returns one or more saved memory IDs, make the save visible in the final user-facing answer with a compact `EchoMem saved:` list containing every memory created by that call. Link each memory key to its canonical `https://echoknows.com/memory/<memory-id>` URL. This save receipt is separate from `EchoMem sources:` and does not imply the newly saved memories informed the answer.',
|
|
363
|
-
"- For a user with a company group, call `request_group_session_sharing` near conversation start or after a qualifying private save. On the first call in a conversation, omit `groupSharingScopeId` so EchoMem mints a fresh opaque scope. Reuse that exact scope only in this conversation for later get/request/set/save calls, and never persist it as memory. If multiple groups are returned, reuse the same scope and call once per `groupId`.
|
|
363
|
+
"- For a user with a company group, call `request_group_session_sharing` near conversation start or after a qualifying private save. On the first call in a conversation, omit `groupSharingScopeId` so EchoMem mints a fresh opaque scope. Reuse that exact scope only in this conversation for later get/request/set/save calls, and never persist it as memory. If multiple groups are returned, reuse the same scope and call once per `groupId`. Relay the tool's exact text question and call `set_group_session_sharing` only after an explicit Yes/No. Silence leaves that group's state unset; never infer an answer. Saves sync eligible memories to every approved group; a No keeps them private for that group.",
|
|
364
364
|
"- 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.",
|
|
365
365
|
"",
|
|
366
366
|
"### Company group memory",
|
|
@@ -416,7 +416,26 @@ export function writeJsonClientConfig(configPath, entry) {
|
|
|
416
416
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
417
417
|
}
|
|
418
418
|
export function writeClaudeCodeConfig(entry) {
|
|
419
|
-
|
|
419
|
+
// EchoMem is a memory server that should load in EVERY Claude Code project, so it belongs at
|
|
420
|
+
// `user` scope (~/.claude.json, all projects) rather than `local` scope (the current project only).
|
|
421
|
+
const addArguments = ["mcp", "add-json", "-s", "user", "echomem", JSON.stringify(entry)];
|
|
422
|
+
const removeFromScope = (scope) => {
|
|
423
|
+
try {
|
|
424
|
+
execFileSync("claude", ["mcp", "remove", "echomem", "-s", scope], {
|
|
425
|
+
encoding: "utf8",
|
|
426
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
427
|
+
timeout: 10000,
|
|
428
|
+
});
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
// Older builds installed EchoMem at `local` scope. Left in place it would shadow the user-scoped
|
|
436
|
+
// entry and keep launching the stale command, so drop it first. Best-effort: `local` is per-project,
|
|
437
|
+
// so this only clears the directory setup runs from — a no-op (ignored) when nothing is there.
|
|
438
|
+
removeFromScope("local");
|
|
420
439
|
try {
|
|
421
440
|
execFileSync("claude", addArguments, {
|
|
422
441
|
encoding: "utf8",
|
|
@@ -431,14 +450,11 @@ export function writeClaudeCodeConfig(entry) {
|
|
|
431
450
|
if (!detail.includes("already exists"))
|
|
432
451
|
return "unavailable";
|
|
433
452
|
}
|
|
434
|
-
// Claude Code's CLI will not replace a same-name
|
|
435
|
-
//
|
|
453
|
+
// Claude Code's CLI will not replace a same-name server. Once the replacement entry is fully
|
|
454
|
+
// constructed, remove only EchoMem and immediately re-add it; sibling MCP servers remain.
|
|
436
455
|
try {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
440
|
-
timeout: 10000,
|
|
441
|
-
});
|
|
456
|
+
if (!removeFromScope("user"))
|
|
457
|
+
return "unavailable";
|
|
442
458
|
execFileSync("claude", addArguments, {
|
|
443
459
|
encoding: "utf8",
|
|
444
460
|
stdio: ["ignore", "pipe", "pipe"],
|
package/dist/v1-contract.js
CHANGED
|
@@ -50,6 +50,7 @@ const READ_ONLY_TOOL_NAMES = new Set([
|
|
|
50
50
|
canonicalToolNames.publicMemory,
|
|
51
51
|
canonicalToolNames.groupContext,
|
|
52
52
|
canonicalToolNames.getGroupSessionSharing,
|
|
53
|
+
canonicalToolNames.requestGroupSessionSharing,
|
|
53
54
|
canonicalToolNames.getByContext,
|
|
54
55
|
canonicalToolNames.checkpointByContext,
|
|
55
56
|
canonicalToolNames.report,
|
|
@@ -59,7 +60,6 @@ const READ_ONLY_TOOL_NAMES = new Set([
|
|
|
59
60
|
]);
|
|
60
61
|
const IDEMPOTENT_WRITE_TOOL_NAMES = new Set([
|
|
61
62
|
canonicalToolNames.recordCitations,
|
|
62
|
-
canonicalToolNames.requestGroupSessionSharing,
|
|
63
63
|
canonicalToolNames.setGroupSessionSharing,
|
|
64
64
|
canonicalToolNames.updateGroupProfile,
|
|
65
65
|
canonicalToolNames.completeGroupPublication,
|
|
@@ -417,7 +417,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
417
417
|
},
|
|
418
418
|
{
|
|
419
419
|
name: canonicalToolNames.save,
|
|
420
|
-
description: `Save durable knowledge from this conversation into the user's private EchoMem (durable memories are extracted automatically). Call before the final response when work produced a durable decision, confirmed preference, implementation, resolved bug, commit, passing verification, release, or project milestone; this private checkpoint does not require a separate user request. Omit secrets, incidental chatter, and trivial read-only work. If the encrypted vault is locked, tell the user to ${MCP_VAULT_UNLOCK_INSTRUCTION} and never silently skip a qualifying checkpoint. Private persistence happens first. For group sharing, reuse the exact groupSharingScopeId returned by get_group_session_sharing, request_group_session_sharing, or an earlier save in this conversation. Never reuse it in another conversation or save it as memory. Each group has an independent decision under the same conversation scope; eligible memories sync automatically to every approved group, while flagged memories stay private. If a selected group has no decision yet, call request_group_session_sharing
|
|
420
|
+
description: `Save durable knowledge from this conversation into the user's private EchoMem (durable memories are extracted automatically). Call before the final response when work produced a durable decision, confirmed preference, implementation, resolved bug, commit, passing verification, release, or project milestone; this private checkpoint does not require a separate user request. Omit secrets, incidental chatter, and trivial read-only work. If the encrypted vault is locked, tell the user to ${MCP_VAULT_UNLOCK_INSTRUCTION} and never silently skip a qualifying checkpoint. Private persistence happens first. For group sharing, reuse the exact groupSharingScopeId returned by get_group_session_sharing, request_group_session_sharing, or an earlier save in this conversation. Never reuse it in another conversation or save it as memory. Each group has an independent decision under the same conversation scope; eligible memories sync automatically to every approved group, while flagged memories stay private. If a selected group has no decision yet, call request_group_session_sharing, relay its exact text question, and call set_group_session_sharing only after an explicit Yes or No. Silence leaves consent unset; never infer the answer. New extraction input uses the plan's weekly processing allowance; if the limit is reached, nothing is saved. passthrough=true stores the text verbatim as a session capsule. ${SAVED_MEMORY_RECEIPT_INSTRUCTION}`,
|
|
421
421
|
inputSchema: {
|
|
422
422
|
type: "object",
|
|
423
423
|
properties: {
|
|
@@ -661,7 +661,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
661
661
|
},
|
|
662
662
|
{
|
|
663
663
|
name: canonicalToolNames.getGroupSessionSharing,
|
|
664
|
-
description: "Read the confirmed sharing decision for one group in this exact conversation. On the first call, omit groupSharingScopeId; EchoMem returns a new opaque scope. Reuse that scope only in this conversation. If the user belongs to multiple groups and groupId is omitted, EchoMem returns the available groups. If a decision is unset, call request_group_session_sharing with this scope and the selected groupId
|
|
664
|
+
description: "Read the confirmed sharing decision for one group in this exact conversation. On the first call, omit groupSharingScopeId; EchoMem returns a new opaque scope. Reuse that scope only in this conversation. If the user belongs to multiple groups and groupId is omitted, EchoMem returns the available groups. If a decision is unset, call request_group_session_sharing with this scope and the selected groupId, relay its exact text question, and call set_group_session_sharing only after an explicit Yes or No. Silence leaves that group's state unset; never infer the answer.",
|
|
665
665
|
inputSchema: {
|
|
666
666
|
type: "object",
|
|
667
667
|
properties: {
|
|
@@ -682,7 +682,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
682
682
|
},
|
|
683
683
|
{
|
|
684
684
|
name: canonicalToolNames.requestGroupSessionSharing,
|
|
685
|
-
description: "Check one group's sharing decision for this exact conversation and, only when it is unset,
|
|
685
|
+
description: "Check one group's sharing decision for this exact conversation and, only when it is unset, return the exact text confirmation question the agent must relay to the user. Omit groupSharingScopeId only on the first call so EchoMem can mint a fresh scope; reuse the returned scope only in this conversation. When multiple groups are returned, call again with the same scope and one groupId at a time. Call set_group_session_sharing only after the user explicitly answers Yes or No. A confirmed Yes backfills eligible private memories and syncs later saves; a confirmed No keeps them private. Silence records nothing.",
|
|
686
686
|
inputSchema: {
|
|
687
687
|
type: "object",
|
|
688
688
|
properties: {
|
|
@@ -743,7 +743,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
743
743
|
},
|
|
744
744
|
{
|
|
745
745
|
name: canonicalToolNames.joinGroup,
|
|
746
|
-
description: "Join a company memory group using an invite code after the user explicitly asks to join. Joining never publishes memories. Next call prepare_group_publication, infer a proposed title and responsibility summary from the user's own candidate memories, and ask the user to confirm the profile together with the publication preview. Also call request_group_session_sharing without a scope
|
|
746
|
+
description: "Join a company memory group using an invite code after the user explicitly asks to join. Joining never publishes memories. Next call prepare_group_publication, infer a proposed title and responsibility summary from the user's own candidate memories, and ask the user to confirm the profile together with the publication preview. Also call request_group_session_sharing without a scope and relay its exact text question. Silence stays unset and should be prompted again at a later qualifying checkpoint.",
|
|
747
747
|
inputSchema: {
|
|
748
748
|
type: "object",
|
|
749
749
|
properties: {
|
package/package.json
CHANGED
|
@@ -27,8 +27,7 @@ Use the `save_conversation` tool from the `echomem` MCP server.
|
|
|
27
27
|
after a qualifying private save. On the first call in a conversation, omit `groupSharingScopeId` so
|
|
28
28
|
EchoMem mints a fresh opaque scope. Reuse that exact scope only in this conversation for later
|
|
29
29
|
get/request/set/save calls, and never persist it as memory. If multiple groups are returned, reuse
|
|
30
|
-
the same scope and call once per `groupId`.
|
|
31
|
-
|
|
32
|
-
`set_group_session_sharing` only after an explicit Yes/No. Decline, cancel, or silence leaves that
|
|
30
|
+
the same scope and call once per `groupId`. Relay the tool's exact text question and call
|
|
31
|
+
`set_group_session_sharing` only after an explicit Yes/No. Silence leaves that
|
|
33
32
|
group's state unset; never infer an answer. Saves sync eligible memories to every approved group;
|
|
34
33
|
Keep private keeps them private for that group; flagged memories remain private.
|
|
@@ -27,8 +27,7 @@ This project has EchoMem connected — the user's long-term memory across all th
|
|
|
27
27
|
after a qualifying private save. On the first call in a conversation, omit `groupSharingScopeId` so
|
|
28
28
|
EchoMem mints a fresh opaque scope. Reuse that exact scope only in this conversation for later
|
|
29
29
|
get/request/set/save calls, and never persist it as memory. If multiple groups are returned, reuse
|
|
30
|
-
the same scope and call once per `groupId`.
|
|
31
|
-
|
|
32
|
-
`set_group_session_sharing` only after an explicit Yes/No. Decline, cancel, or silence leaves that
|
|
30
|
+
the same scope and call once per `groupId`. Relay the tool's exact text question and call
|
|
31
|
+
`set_group_session_sharing` only after an explicit Yes/No. Silence leaves that
|
|
33
32
|
group's state unset; never infer an answer. Saves sync eligible memories to every approved group;
|
|
34
33
|
Keep private keeps them private for that group; flagged memories stay private.
|