fix(plugins): use shared handoff resources and native patch versions
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.2
|
||||
## 0.3.1
|
||||
|
||||
- An explicit `mem0_handoff` call uses the current DeepSeek session’s derived messages; invoke it outside nested code mode. Uses the [shared handoff logic](../agent-plugin-core/CHANGELOG.md#032).
|
||||
- List and resume shared resources from any supported plugin; the destination is the common local store.
|
||||
- Lightened search prompts: search when prior work may help; repeat only for a specific gap.
|
||||
|
||||
@@ -10,19 +10,19 @@ It gives a Harness agent automatic long-term memory plus two explicit memory too
|
||||
| Auto-capture | Stores the human/assistant messages from each completed turn |
|
||||
| `search_memory` | Recall facts from Mem0 relevant to a query |
|
||||
| `add_memory` | Store a fact in Mem0 for future sessions |
|
||||
| `mem0_handoff` | Continue the current DeepSeek session in Codex |
|
||||
| `mem0_handoff` | Save, list, or resume shared session context |
|
||||
|
||||
Unlike the local/file-based memory plugins in the ecosystem, Mem0 is a managed backend: server-side extraction, semantic dedup and conflict resolution, and memories that other agents can retrieve when their user and entity filters match.
|
||||
|
||||
Current package version: `0.3.2`.
|
||||
Current package version: `0.3.1`.
|
||||
|
||||
## Session handoff
|
||||
|
||||
Explicitly request the `mem0_handoff` tool to continue the current DeepSeek session in Codex. The tool uses the native session context and completed tool outcomes. Invoke it directly; nested code-mode calls are rejected when the enclosing program is still running.
|
||||
Use `mem0_handoff` with action `save`, `list`, or `resume` (with a resource path). Save reads the current DeepSeek session; invoke save directly, outside a running nested code-mode call.
|
||||
|
||||
Requires **Python 3.11+** available as `python3` and an installed, signed-in Codex CLI with native session import support. Handoff works independently of Mem0 credentials and never sends the transcript through the Mem0 API. Unsupported content, missing results, and unrelated unfinished calls fail explicitly.
|
||||
All plugins share local resources in `~/.mem0/handoffs/`, preserving supported active context, images, and completed tool outcomes. Resume reads that context as historical evidence. Requires **Python 3.11+** as `python3`; no destination CLI or Mem0 credentials are required.
|
||||
|
||||
The shared launcher and pinned runtime manifest are packaged in `dist/` by [agent-plugin-core](../agent-plugin-core/README.md); the importer is fetched once from its pinned GitHub commit, verified, and cached for all plugins. A valid cache works offline. Failed imports save a private recovery bundle under `~/.mem0/handoffs/`. See the [session handoff guide](../agent-plugin-core/README.md#session-handoff) for supported formats, privacy, and recovery.
|
||||
The shared engine is fetched from a pinned GitHub commit on first use, verified, and cached across all plugins. Cached use works offline; no transcript is sent to GitHub. See the [shared handoff logic](../agent-plugin-core/README.md#session-handoff) for source formats and validation.
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -61,7 +61,7 @@ Cordis owns listener and tool cleanup when the plugin unmounts. Every automatic
|
||||
3. Install it into a disposable Harness profile:
|
||||
```sh
|
||||
DSH_HOME=/tmp/mem0-dsh-dev pnpm dlx @deepseek-ai/dsh@0.1.1-rc.2 \
|
||||
plugin --profile headless add /tmp/mem0-deepseek-plugin/mem0-deepseek-plugin-0.3.2.tgz
|
||||
plugin --profile headless add /tmp/mem0-deepseek-plugin/mem0-deepseek-plugin-0.3.1.tgz
|
||||
```
|
||||
4. Copy `cordis.example.yml`, set its installed package path and your `userId`, then run Harness with the same profile:
|
||||
```sh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/deepseek-plugin",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.1",
|
||||
"description": "Mem0 long-term memory as a native DeepSeek Harness (Cordis) plugin.",
|
||||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -21,7 +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 { buildHandoffBundle, runHandoff, runHandoffAction } from "../../agent-plugin-core/typescript/src/handoff.ts";
|
||||
import { createMemoryLifecycle } from "../../agent-plugin-core/typescript/src/lifecycle.ts";
|
||||
|
||||
export const name = "mem0";
|
||||
@@ -95,15 +95,20 @@ 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: {},
|
||||
description: "On explicit user request, save the current session as a shared handoff resource, list resources for this project, or resume one resource into the current conversation as historical context. Requires Python 3.11+.",
|
||||
parameters: {
|
||||
action: {type: "string", enum: ["save", "list", "resume"], description: "Defaults to save; resume loads a shared resource into this conversation."},
|
||||
resource: {type: "string", description: "Resource path returned by save or list; required for resume."},
|
||||
},
|
||||
output: textOutput,
|
||||
async execute(_args, exec) {
|
||||
async execute({action = "save", resource}, 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.");
|
||||
if (action === "list" || action === "resume") return await runHandoffAction(new URL("./session_handoff.py", import.meta.url), action, session.header.cwd, resource);
|
||||
if (action !== "save") throw new Error("Handoff action must be save, list, or resume.");
|
||||
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");
|
||||
|
||||
@@ -2,9 +2,9 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
// Offline mock of the Mem0 SDK so these tests never touch the network.
|
||||
vi.mock("../../agent-plugin-core/typescript/src/handoff.ts", async (original) => ({
|
||||
...await original<typeof import("../../agent-plugin-core/typescript/src/handoff.ts")>(), runHandoff: vi.fn(),
|
||||
...await original<typeof import("../../agent-plugin-core/typescript/src/handoff.ts")>(), runHandoff: vi.fn(), runHandoffAction: vi.fn(),
|
||||
}));
|
||||
import { runHandoff } from "../../agent-plugin-core/typescript/src/handoff.ts";
|
||||
import { runHandoff, runHandoffAction } from "../../agent-plugin-core/typescript/src/handoff.ts";
|
||||
|
||||
const mockSearch = vi.fn();
|
||||
const mockAdd = vi.fn();
|
||||
@@ -290,9 +290,9 @@ describe("mem0_handoff tool", () => {
|
||||
],
|
||||
}}};
|
||||
it("exports the current native session, excluding only its own in-flight call", async () => {
|
||||
vi.mocked(runHandoff).mockResolvedValue("Created Codex task");
|
||||
vi.mocked(runHandoff).mockResolvedValue("Saved shared resource");
|
||||
const tools = applyAndCollect({apiKey: "k", userId: "u"});
|
||||
expect(await tools.get("mem0_handoff")!.execute({}, exec)).toBe("Created Codex task");
|
||||
expect(await tools.get("mem0_handoff")!.execute({}, exec)).toBe("Saved shared resource");
|
||||
expect(runHandoff).toHaveBeenCalledWith(expect.any(URL), expect.objectContaining({
|
||||
source: expect.objectContaining({host: "deepseek", session_id: "native-session", title: "Renamed native task"}),
|
||||
items: [{type: "message", role: "user", content: [{type: "input_text", text: "Readable current context"}]}],
|
||||
@@ -300,6 +300,16 @@ describe("mem0_handoff tool", () => {
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
expect(mockAdd).not.toHaveBeenCalled();
|
||||
});
|
||||
it.each(["list", "resume"])("%s consumes shared resources in the active model without exporting its session", async (action) => {
|
||||
delete process.env.MEM0_API_KEY;
|
||||
vi.mocked(runHandoffAction).mockResolvedValue("Complete historical context and tool outcomes");
|
||||
const tools = applyAndCollect({userId: "u"});
|
||||
expect(await tools.get("mem0_handoff")!.execute({action, resource: "/tmp/shared task.json"}, exec)).toBe("Complete historical context and tool outcomes");
|
||||
expect(runHandoffAction).toHaveBeenCalledWith(expect.any(URL), action, "/tmp", "/tmp/shared task.json");
|
||||
expect(runHandoff).not.toHaveBeenCalled();
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
expect(mockAdd).not.toHaveBeenCalled();
|
||||
});
|
||||
it("refuses unfinished sibling tools rather than hiding them with its own invocation", async () => {
|
||||
const tools = applyAndCollect({apiKey: "k", userId: "u"});
|
||||
const siblingExec = {...exec, agent: {session: {...exec.agent.session, deriveMessages: () => [
|
||||
|
||||
Reference in New Issue
Block a user