fix(pi-agent-plugin): make command results visible and relevance-filtered (#5504)

This commit is contained in:
Kartik
2026-06-12 19:13:31 +05:30
committed by GitHub
parent b5ec46be5b
commit f681889b14
9 changed files with 383 additions and 236 deletions
+117 -69
View File
@@ -9,6 +9,8 @@ import { acquireDreamLock } from "./dream/index.ts";
import { CONFIG_DIR } from "./config/index.ts";
import { captureCommandEvent } from "./telemetry.ts";
const SEARCH_TOP_K = 10;
export function registerCommands(
pi: ExtensionAPI,
mem0: MemoryClient,
@@ -16,7 +18,24 @@ export function registerCommands(
getScopeCtx: () => ScopeContext,
telemetryCtx?: { apiKey?: string },
): void {
// ── /mem0-remember ──────────────────────────────────────────────────
const sendFeedback = (customType: string, content: string): void => {
pi.sendMessage({ customType, content, display: true });
};
const pluralize = (n: number, one: string, many: string): string =>
`${n} ${n === 1 ? one : many}`;
const searchMemories = async (query: string, scope: Scope) => {
const filters = resolveSearchFilters(scope, getScopeCtx());
const result = await mem0.search(query, {
filters,
threshold: config.searchThreshold,
topK: SEARCH_TOP_K,
rerank: true,
});
return result.results ?? [];
};
pi.registerCommand("mem0-remember", {
description: "Store a memory verbatim (no inference)",
handler: async (args, ctx) => {
@@ -26,19 +45,24 @@ export function registerCommands(
return;
}
const scopeCtx = getScopeCtx();
const addParams = resolveAddParams(config.defaultScope, scopeCtx);
const addParams = resolveAddParams(config.defaultScope, getScopeCtx());
const result = await mem0.add(
[{ role: "user", content: text }],
{ ...addParams, customCategories: DEFAULT_CUSTOM_CATEGORIES, infer: false },
);
const msg = (result as { message?: string }).message ?? "Memory stored.";
captureCommandEvent("mem0-remember", {}, telemetryCtx);
ctx.ui.notify(msg, "info");
const storedItems = (Array.isArray(result) ? result : [])
.map((m) => (m as { memory?: string }).memory)
.filter((m): m is string => Boolean(m));
const items = storedItems.length > 0 ? storedItems : [text];
sendFeedback(
"mem0-remember",
[`**Stored to ${config.defaultScope} memory**`, ...items.map((m) => `- ${m}`)].join("\n"),
);
},
});
// ── /mem0-forget ────────────────────────────────────────────────────
pi.registerCommand("mem0-forget", {
description: "Delete memories matching a natural language query",
handler: async (args, ctx) => {
@@ -48,49 +72,51 @@ export function registerCommands(
return;
}
const scopeCtx = getScopeCtx();
const filters = resolveSearchFilters(config.defaultScope, scopeCtx);
const result = await mem0.search(query, { filters });
const memories = result.results ?? [];
const memories = await searchMemories(query, config.defaultScope);
if (memories.length === 0) {
captureCommandEvent("mem0-forget", { result_count: 0 }, telemetryCtx);
ctx.ui.notify("No matching memories found.", "info");
sendFeedback("mem0-forget", `**No matches for "${query}"** — nothing to forget.`);
return;
}
const forgotten = (mem: Parameters<typeof formatMemoryCompact>[0]) => {
captureCommandEvent("mem0-forget", { deleted_count: 1 }, telemetryCtx);
sendFeedback(
"mem0-forget",
[`**Forgotten from ${config.defaultScope} memory**`, `- ${formatMemoryCompact(mem)}`].join("\n"),
);
};
if (memories.length === 1) {
const target = memories[0];
const confirmed = await ctx.ui.confirm(
"Delete this memory?",
formatMemoryCompact(target),
);
const confirmed = await ctx.ui.confirm("Delete this memory?", formatMemoryCompact(target));
if (!confirmed) {
ctx.ui.notify("Cancelled.", "info");
sendFeedback("mem0-forget", "**Cancelled** — no memories deleted.");
return;
}
await mem0.delete(target.id);
captureCommandEvent("mem0-forget", { deleted_count: 1 }, telemetryCtx);
ctx.ui.notify(`Deleted: ${formatMemoryCompact(target)}`, "info");
forgotten(target);
return;
}
const labels = memories.map((m) => formatMemoryCompact(m));
const selected = await ctx.ui.select("Which memory should I delete?", labels);
const selected = await ctx.ui.select(
`Found ${pluralize(memories.length, "match", "matches")} for "${query}" — which should I delete?`,
labels,
);
if (!selected) {
ctx.ui.notify("Cancelled.", "info");
sendFeedback("mem0-forget", "**Cancelled** — no memories deleted.");
return;
}
const idx = labels.indexOf(selected);
if (idx < 0) return;
const target = memories[idx];
await mem0.delete(target.id);
captureCommandEvent("mem0-forget", { deleted_count: 1 }, telemetryCtx);
ctx.ui.notify(`Deleted: ${formatMemoryCompact(target)}`, "info");
forgotten(target);
},
});
// ── /mem0-search ────────────────────────────────────────────────────
pi.registerCommand("mem0-search", {
description: "Semantic search across memories",
handler: async (args, ctx) => {
@@ -100,21 +126,25 @@ export function registerCommands(
return;
}
const scopeCtx = getScopeCtx();
const filters = resolveSearchFilters(config.defaultScope, scopeCtx);
const result = await mem0.search(query, { filters });
const memories = result.results ?? [];
const memories = await searchMemories(query, config.defaultScope);
captureCommandEvent("mem0-search", { result_count: memories.length }, telemetryCtx);
pi.sendMessage({
customType: "mem0-search",
content: formatMemoryList(memories),
display: true,
});
if (memories.length === 0) {
sendFeedback("mem0-search", `**No matches for "${query}"** · ${config.defaultScope} scope`);
return;
}
sendFeedback(
"mem0-search",
[
`**${pluralize(memories.length, "match", "matches")} for "${query}"** · ${config.defaultScope} scope`,
"",
formatMemoryList(memories),
].join("\n"),
);
},
});
// ── /mem0-tour ──────────────────────────────────────────────────────
pi.registerCommand("mem0-tour", {
description: "Browse all memories grouped by category",
handler: async (args, ctx) => {
@@ -125,19 +155,21 @@ export function registerCommands(
return;
}
const scope: Scope = (raw as Scope) || config.defaultScope;
const scopeCtx = getScopeCtx();
const filters = resolveSearchFilters(scope, scopeCtx);
const filters = resolveSearchFilters(scope, getScopeCtx());
const result = await mem0.getAll({ filters });
const memories = result.results ?? [];
if (memories.length === 0) {
captureCommandEvent("mem0-tour", { memory_count: 0, scope }, telemetryCtx);
pi.sendMessage({ customType: "mem0-tour", content: "No memories found.", display: true });
sendFeedback("mem0-tour", `**No memories in ${scope} scope yet** — store one with \`/mem0-remember\`.`);
return;
}
const groups = groupByCategory(memories);
const lines: string[] = [`**Memory Tour** (${memories.length} total, scope: ${scope})`, ""];
const lines: string[] = [
`**Memory tour** · ${pluralize(memories.length, "memory", "memories")} · ${scope} scope`,
"",
];
for (const [category, items] of groups) {
lines.push(`### ${category} (${items.length})`);
@@ -148,11 +180,10 @@ export function registerCommands(
}
captureCommandEvent("mem0-tour", { memory_count: memories.length, scope }, telemetryCtx);
pi.sendMessage({ customType: "mem0-tour", content: lines.join("\n"), display: true });
sendFeedback("mem0-tour", lines.join("\n"));
},
});
// ── /mem0-dream ─────────────────────────────────────────────────────
pi.registerCommand("mem0-dream", {
description: "Consolidate memories — merge duplicates, prune stale entries, resolve contradictions",
handler: async (_args, ctx) => {
@@ -162,12 +193,14 @@ export function registerCommands(
}
captureCommandEvent("mem0-dream", {}, telemetryCtx);
pi.sendMessage({ customType: "mem0-dream", content: DREAM_PROTOCOL, display: true }, { triggerTurn: true });
ctx.ui.notify("Dream consolidation started.", "info");
pi.sendMessage({ customType: "mem0-dream", content: DREAM_PROTOCOL, display: false }, { triggerTurn: true });
sendFeedback(
"mem0-dream",
"**Dreaming** — reviewing your memories to merge duplicates, resolve contradictions, and prune stale entries. I'll report what changed.",
);
},
});
// ── /mem0-pin ───────────────────────────────────────────────────────
pi.registerCommand("mem0-pin", {
description: "Pin a memory to protect it from dream pruning",
handler: async (args, ctx) => {
@@ -177,42 +210,49 @@ export function registerCommands(
return;
}
const scopeCtx = getScopeCtx();
const filters = resolveSearchFilters(config.defaultScope, scopeCtx);
const result = await mem0.search(query, { filters });
const memories = result.results ?? [];
const memories = await searchMemories(query, config.defaultScope);
if (memories.length === 0) {
captureCommandEvent("mem0-pin", { result_count: 0 }, telemetryCtx);
ctx.ui.notify("No matching memories found to pin.", "info");
sendFeedback("mem0-pin", `**No matches for "${query}"** — nothing to pin.`);
return;
}
const pinned = (mem: Parameters<typeof formatMemoryCompact>[0]) => {
captureCommandEvent("mem0-pin", { pinned: true }, telemetryCtx);
sendFeedback(
"mem0-pin",
["**Pinned** — protected from dream pruning", `- ${formatMemoryCompact(mem)}`].join("\n"),
);
};
const alreadyPinned = (mem: Parameters<typeof formatMemoryCompact>[0]) => {
sendFeedback("mem0-pin", ["**Already pinned**", `- ${formatMemoryCompact(mem)}`].join("\n"));
};
if (memories.length === 1) {
const target = memories[0];
const text = target.memory ?? "";
if (text.startsWith("[PINNED]")) {
ctx.ui.notify("Already pinned.", "info");
alreadyPinned(target);
return;
}
const confirmed = await ctx.ui.confirm(
"Pin this memory?",
formatMemoryCompact(target),
);
const confirmed = await ctx.ui.confirm("Pin this memory?", formatMemoryCompact(target));
if (!confirmed) {
ctx.ui.notify("Cancelled.", "info");
sendFeedback("mem0-pin", "**Cancelled** — nothing was pinned.");
return;
}
await mem0.update(target.id, { text: `[PINNED] ${text}` });
captureCommandEvent("mem0-pin", { pinned: true }, telemetryCtx);
ctx.ui.notify(`Pinned: ${formatMemoryCompact(target)}`, "info");
pinned(target);
return;
}
const labels = memories.map((m) => formatMemoryCompact(m));
const selected = await ctx.ui.select("Which memory should I pin?", labels);
const selected = await ctx.ui.select(
`Found ${pluralize(memories.length, "match", "matches")} for "${query}" — which should I pin?`,
labels,
);
if (!selected) {
ctx.ui.notify("Cancelled.", "info");
sendFeedback("mem0-pin", "**Cancelled** — nothing was pinned.");
return;
}
const idx = labels.indexOf(selected);
@@ -220,16 +260,14 @@ export function registerCommands(
const target = memories[idx];
const selectedText = target.memory ?? "";
if (selectedText.startsWith("[PINNED]")) {
ctx.ui.notify("Already pinned.", "info");
alreadyPinned(target);
return;
}
await mem0.update(target.id, { text: `[PINNED] ${selectedText}` });
captureCommandEvent("mem0-pin", { pinned: true }, telemetryCtx);
ctx.ui.notify(`Pinned: ${formatMemoryCompact(target)}`, "info");
pinned(target);
},
});
// ── /mem0-scope ─────────────────────────────────────────────────────
pi.registerCommand("mem0-scope", {
description: "Change default memory scope for this session (project, session, global)",
handler: async (args, ctx) => {
@@ -237,7 +275,13 @@ export function registerCommands(
const valid: Scope[] = ["project", "session", "global"];
if (!scope) {
ctx.ui.notify(`Current scope: ${config.defaultScope}. Usage: /mem0-scope <${valid.join("|")}>`, "info");
sendFeedback(
"mem0-scope",
[
`**Current scope: ${config.defaultScope}**`,
`New memories save to the **${config.defaultScope}** pool. Switch with \`/mem0-scope <${valid.join(" | ")}>\`.`,
].join("\n"),
);
return;
}
@@ -248,11 +292,16 @@ export function registerCommands(
config.defaultScope = scope as Scope;
captureCommandEvent("mem0-scope", { scope }, telemetryCtx);
ctx.ui.notify(`Default scope changed to "${scope}" for this session.`, "info");
sendFeedback(
"mem0-scope",
[
`**Scope changed to ${scope}**`,
`New memories now save to the **${scope}** pool for this session.`,
].join("\n"),
);
},
});
// ── /mem0-status ────────────────────────────────────────────────────
pi.registerCommand("mem0-status", {
description: "Show connection health, identity, project, and memory count",
handler: async (_args, _ctx) => {
@@ -270,22 +319,21 @@ export function registerCommands(
}
const lines = [
"**Mem0 Status**",
"**Mem0 status**",
"",
`- Connection: ${connected ? "connected" : "disconnected"}`,
`- User: ${scopeCtx.userId}`,
`- Project: ${scopeCtx.appId}`,
`- Session: ${scopeCtx.runId}`,
`- Default scope: ${config.defaultScope}`,
`- Search relevance threshold: ${config.searchThreshold}`,
`- Project memories: ${count}`,
`- Auto-capture: ${config.autoCapture ? "on" : "off"}`,
`- Dream: ${config.dream.enabled ? "enabled" : "disabled"}`,
];
captureCommandEvent("mem0-status", { connected, memory_count: count }, telemetryCtx);
pi.sendMessage({ customType: "mem0-status", content: lines.join("\n"), display: true });
sendFeedback("mem0-status", lines.join("\n"));
},
});
}