feat(plugins): share native session handoff and soften memory search

This commit is contained in:
kartik-mem0
2026-09-10 18:20:36 +05:30
parent 02f7a9b2c4
commit 82c32f993e
130 changed files with 16430 additions and 379 deletions
+39 -2
View File
@@ -1,3 +1,4 @@
import { SEARCH_GUIDANCE } from "../../agent-plugin-core/typescript/src/search_guidance.ts";
/**
* deepseek-plugin: Mem0 long-term memory as a native DeepSeek Harness (Cordis) plugin.
*
@@ -20,6 +21,7 @@ import { formatMemoryList, formatAddResult } from "./formatting.ts";
import { truncateOutput } from "./output.ts";
import { resolveSearchFilters, resolveAddParams } from "./scoping.ts";
import { captureEvent, errorKind } from "./telemetry.ts";
import { buildHandoffBundle, runHandoff } from "../../agent-plugin-core/typescript/src/handoff.ts";
import { createMemoryLifecycle } from "../../agent-plugin-core/typescript/src/lifecycle.ts";
export const name = "mem0";
@@ -90,9 +92,44 @@ const scopeParams = {
} as const;
export function apply(ctx: Context, config: Config): void {
ctx.tools.register(
defineTool({
name: "mem0_handoff",
description: "Continue the current DeepSeek session in Codex, only when the user explicitly requests a handoff. Preserves the full active conversation and completed tool outcomes. Requires Python 3.11+ and a signed-in Codex CLI.",
parameters: {},
output: textOutput,
async execute(_args, exec) {
try {
if (exec.rootCallId && exec.rootCallId !== exec.callId) throw new Error("Invoke handoff directly, outside a nested code-mode tool call.");
const session = exec.agent?.session;
if (!session) throw new Error("The active DeepSeek session is unavailable.");
if (!session.header.cwd) throw new Error("The native session project directory is unavailable.");
const attachments = (ctx as unknown as { attachments?: { readImage(ref: unknown): Promise<{ref: {mediaType: string}; data: Uint8Array}> } }).attachments;
// dsh-session-title persists user renames and generated titles as last-wins log events.
const titleEvent = [...session.events].reverse().find(event => String(event.type) === "session/title");
const nativeTitle = (titleEvent?.data as {title?: unknown} | undefined)?.title;
if (titleEvent && (typeof nativeTitle !== "string" || !nativeTitle.trim())) throw new Error("The native session title is invalid.");
const bundle = await buildHandoffBundle({
host: "deepseek", session_id: session.id,
title: typeof nativeTitle === "string" ? nativeTitle : `DeepSeek session ${session.id}`, cwd: session.header.cwd,
}, session.deriveMessages(), {
excludeCallId: exec.callId,
readImage: async (ref) => {
if (!attachments) throw new Error("DeepSeek image attachment storage is unavailable.");
const image = await attachments.readImage(ref);
return { data: image.data, mediaType: image.ref.mediaType };
},
});
return await runHandoff(new URL("./session_handoff.py", import.meta.url), bundle);
} catch (error) {
return `Session handoff failed: ${error instanceof Error ? error.message : String(error)}`;
}
},
}),
);
const apiKey = config.apiKey ?? process.env.MEM0_API_KEY;
if (!apiKey) {
throw new Error("deepseek-plugin: set config.apiKey or the MEM0_API_KEY env var");
return; // Local handoff remains available before memory is configured.
}
const userId = config.userId?.trim();
if (!userId || /^\*+$/.test(userId)) {
@@ -199,7 +236,7 @@ export function apply(ctx: Context, config: Config): void {
defineTool({
name: "search_memory",
description:
"Search the user's long-term Mem0 memory for facts relevant to a query. Use proactively before answering anything that may depend on what the user told you earlier.",
SEARCH_GUIDANCE,
parameters: {
query: { type: "string", description: "What to recall.", required: true },
limit: {