fix(plugins): use shared handoff resources and native patch versions

This commit is contained in:
kartik-mem0
2026-09-10 19:20:14 +05:30
parent 19a71bbacf
commit 2ac8f2f9a9
83 changed files with 2515 additions and 2916 deletions
+3 -2
View File
@@ -1,6 +1,7 @@
# Changelog
## 0.3.2
## 0.3.1
- `/mem0-handoff codex` uses Pi’s selected branch and native compaction context (Node.js 22.19+). Uses the [shared handoff logic](../agent-plugin-core/CHANGELOG.md#032).
- `/mem0-handoff` uses Pi’s selected branch and native compaction context (Node.js 22.19+). 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.
+5 -7
View File
@@ -4,7 +4,7 @@ Persistent semantic memory for [Pi Agent](https://pi.dev), powered by [Mem0](htt
This extension gives Pi Agent long-term memory that persists across sessions, projects, and devices. Memories are automatically captured from conversations and can be searched and managed through slash commands and an agent-accessible tool.
Current package version: `0.3.2`. Shared redaction and lifecycle utilities come from [agent-plugin-core](../agent-plugin-core/README.md); Pi keeps its own tools and scopes.
Current package version: `0.3.1`. Shared redaction and lifecycle utilities come from [agent-plugin-core](../agent-plugin-core/README.md); Pi keeps its own tools and scopes.
## Features
@@ -54,13 +54,11 @@ Environment variables (`MEM0_API_KEY`, `MEM0_USER_ID`) override the config file.
## Session handoff
Run `/mem0-handoff codex` to continue the current Pi session in Codex. The plugin reads native session context, including readable compaction summaries and completed tool outcomes.
Run `/mem0-handoff` to save the current session, `/mem0-handoff list` to find this project’s resources, or `/mem0-handoff resume /absolute/path.json` to continue from one.
Handoff requires **Node.js 22.19+**, matching the native Pi SDK. The SDK loads only when handoff is invoked; existing memory features remain available on Node.js 20.
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. Pi save requires Node.js 22.19+ for its native SDK; list, resume, and memory features remain available on Node.js 20.
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.
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.
## Commands
@@ -72,7 +70,7 @@ The shared launcher and pinned runtime manifest are packaged in `dist/` by [agen
| `/mem0-tour [scope]` | Browse all memories grouped by category |
| `/mem0-scope <scope>` | Change default scope for this session |
| `/mem0-status` | Connection health, identity, and memory count |
| `/mem0-handoff codex` | Continue the current Pi session in Codex |
| `/mem0-handoff [save|list|resume <path>]` | Save or resume shared session context |
## Skills
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/pi-agent-plugin",
"version": "0.3.2",
"version": "0.3.1",
"type": "module",
"description": "Mem0 memory extension for Pi Agent persistent, scoped, semantic memory across sessions and projects",
"license": "Apache-2.0",
@@ -1,8 +1,8 @@
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest";
import mem0Extension from "./entry.ts";
import {buildSessionContext, convertToLlm} from "@earendil-works/pi-coding-agent";
import {runHandoff} from "../../agent-plugin-core/typescript/src/handoff.ts";
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()}));
import {runHandoff, runHandoffAction} from "../../agent-plugin-core/typescript/src/handoff.ts";
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(), runHandoffAction: vi.fn()}));
vi.mock("@earendil-works/pi-coding-agent", () => ({
buildSessionContext: vi.fn(() => ({messages: [{role: "user", content: "Native summary and retained tail"}]})),
convertToLlm: vi.fn(messages => messages),
@@ -34,15 +34,15 @@ describe("native Pi handoff", () => {
it("keeps startup working on Node 20 and explains the native handoff runtime requirement", async () => {
vi.stubGlobal("process", {...process, versions: {...process.versions, node: "20.20.2"}});
const {ctx, handler} = setup();
await handler("codex", ctx);
await handler("save", ctx);
expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("Node.js 22.19+"), "error");
expect(buildSessionContext).not.toHaveBeenCalled();
expect(runHandoff).not.toHaveBeenCalled();
});
it("uses host compaction/branch selection and the current session without a Mem0 key", async () => {
const {pi, ctx, handler} = setup();
vi.mocked(runHandoff).mockResolvedValue("Created Codex task");
await handler("codex", ctx);
vi.mocked(runHandoff).mockResolvedValue("Saved shared resource");
await handler("save", ctx);
expect(buildSessionContext).toHaveBeenCalledWith(ctx.sessionManager.getEntries(), "new");
expect(convertToLlm).toHaveBeenCalledOnce();
const bundle = vi.mocked(runHandoff).mock.calls[0][1];
@@ -50,21 +50,33 @@ describe("native Pi handoff", () => {
expect(JSON.stringify(bundle.items)).toContain("Native summary");
expect(JSON.stringify(bundle.items)).toContain("retained tail");
expect(JSON.stringify(bundle.items)).not.toContain("old context");
expect(pi.sendMessage).toHaveBeenCalledWith({customType: "mem0-handoff", content: "Created Codex task", display: true});
expect(pi.sendMessage).toHaveBeenCalledWith({customType: "mem0-handoff", content: "Saved shared resource", display: true});
});
it.each(["", "codex session-id", "claude"])("rejects unsupported arguments %s", async (args) => {
it.each(["resume", "save session-id", "claude"])("rejects unsupported arguments %s", async (args) => {
const {ctx, handler} = setup();
await handler(args, ctx);
expect(runHandoff).not.toHaveBeenCalled();
expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("Usage:"), "warning");
expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("Usage:"), "error");
});
it("reports an active response or importer failure", async () => {
const {pi, ctx, handler} = setup();
await handler("codex", {...ctx, isIdle: () => false});
await handler("save", {...ctx, isIdle: () => false});
expect(runHandoff).not.toHaveBeenCalled();
vi.mocked(runHandoff).mockRejectedValue(new Error("saved at /tmp/retry.json"));
await handler("codex", ctx);
await handler("save", ctx);
expect(ctx.ui.notify).toHaveBeenCalledWith("saved at /tmp/retry.json", "error");
expect(pi.sendMessage).not.toHaveBeenCalled();
});
});
it.each(["list", "resume /tmp/shared task.json"])("supports %s on Node 20 without loading native export helpers", async (args) => {
vi.stubGlobal("process", {...process, versions: {...process.versions, node: "20.20.2"}});
const {pi, ctx, handler} = setup();
vi.mocked(runHandoffAction).mockResolvedValue("Full historical user, assistant and tool context");
await handler(args, ctx);
const resume = args.startsWith("resume");
expect(runHandoffAction).toHaveBeenCalledWith(expect.any(URL), resume ? "resume" : "list", "/tmp", resume ? "/tmp/shared task.json" : undefined);
expect(pi.sendMessage).toHaveBeenCalledWith({customType: "mem0-handoff", content: "Full historical user, assistant and tool context", display: true}, {triggerTurn: resume});
expect(buildSessionContext).not.toHaveBeenCalled();
expect(runHandoff).not.toHaveBeenCalled();
});
+9 -7
View File
@@ -1,22 +1,24 @@
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { buildHandoffBundle, runHandoff } from "../../agent-plugin-core/typescript/src/handoff.ts";
import { buildHandoffBundle, runHandoff, parseHandoffArgs, runHandoffAction } from "../../agent-plugin-core/typescript/src/handoff.ts";
export function registerHandoffCommand(pi: ExtensionAPI): void {
pi.registerCommand("mem0-handoff", {
description: "Continue this Pi session in a new Codex task",
description: "Save, list, or resume shared session handoff resources",
handler: async (args, ctx) => {
if (args.trim() !== "codex") {
ctx.ui.notify("Usage: /mem0-handoff codex", "warning");
return;
}
try {
const {action, resource} = parseHandoffArgs(args);
if (!ctx.isIdle()) throw new Error("Finish the current response before handing off this session.");
const session = ctx.sessionManager;
if (action !== "save") {
const content = await runHandoffAction(new URL("./session_handoff.py", import.meta.url), action, session.getCwd(), resource);
pi.sendMessage({customType: "mem0-handoff", content, display: true}, {triggerTurn: action === "resume"});
return;
}
const [major, minor] = process.versions.node.split(".").map(Number);
if (major < 22 || (major === 22 && minor < 19)) {
throw new Error("Pi session handoff requires Node.js 22.19+ (the native Pi SDK requirement). Memory features remain available.");
}
const { buildSessionContext, convertToLlm } = await import("@earendil-works/pi-coding-agent");
const session = ctx.sessionManager;
const context = buildSessionContext(session.getEntries(), session.getLeafId());
const bundle = await buildHandoffBundle({
host: "pi-agent", session_id: session.getSessionId(),