a2516269cc
CLI now consumes the unified mem0_notice surface that the platform side
emits for unclaimed Agent Mode keys. The notice is a directive to the
LLM agent reading the output, with a verbatim sentence to relay to the
human owner. Two presentation paths:
- Human/text output: yellow stderr banner after the primary output,
once per command. Skipped in agent mode (the JSON envelope carries
it instead, so no duplication).
- JSON/agent output (--json/--agent): folded into the envelope as
"mem0_notice" so an agent parsing the output sees it without
inspecting HTTP headers.
CLI changes (Python + Node, kept in lockstep):
- state.{ts,py}: captureNotice / takeNotice helpers — last-write-wins
stash so multi-request commands fire the notice exactly once.
- backend/platform.{ts,py}: _request extracts notice from response
bodies (top-level dict or list[0]) with header fallback, strips
from downstream payload, captures for end-of-command surfacing.
- output.{ts,py}: JSON envelope formatters fold in any pending notice.
- index.ts / app.py: entrypoint surfaces notice on exit when not in
agent mode.
- commands/agent-mode.{ts,py}: init success path prints the platform's
notice verbatim (fallback to dim claim-command line if a stale
backend doesn't return it).
Init-flag handling fix: the Python argv preprocessor was stripping
--agent from sys.argv unconditionally as the global JSON-output alias.
That swallowed `mem0 init --agent` (where --agent is a subcommand flag
for unattended bootstrap). Now preserved when "init" is in argv.
Parity tests: cli/python/tests/test_agent_mode.py and
cli/node/tests/agent-mode.test.ts — 7 tests each, kept in sync.
cli-spec.json updated: init now lists --agent and --source.
Docs:
- README.md: Agent Mode promo at top of Quickstart.
- docs/llms.txt: fast-path block for AI agents reading the docs.
- skills/mem0/SKILL.md, skills/mem0-cli/SKILL.md,
skills/mem0-integrate/SKILL.md, mem0-plugin/skills/mem0/SKILL.md:
autonomous-setup section + fallback hints.
- mem0-plugin/README.md, openclaw/README.md: "Quick path for agents"
blocks above the human Quick Start.
142 lines
4.6 KiB
TypeScript
142 lines
4.6 KiB
TypeScript
/**
|
|
* Parity tests for `mem0 init --agent` (Agent Mode bootstrap).
|
|
*
|
|
* Mirror of `cli/python/tests/test_agent_mode.py` — both files MUST stay
|
|
* in sync so that the Python and Node CLIs expose an identical surface
|
|
* for the Agent Mode entrypoint. If you add a flag here, add the same
|
|
* assertion on the Python side (and vice versa).
|
|
*
|
|
* Network-bound bootstrap is covered by the platform-side E2E suite
|
|
* (`backend/tests/e2e/test_05_agent_mode.py`); these tests only verify
|
|
* the CLI surface that ships in the binary.
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import { execSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
function run(
|
|
args: string[],
|
|
opts: { home?: string; env?: Record<string, string> } = {},
|
|
): { stdout: string; stderr: string; exitCode: number } {
|
|
const env = { ...process.env };
|
|
for (const key of Object.keys(env)) {
|
|
if (key.startsWith("MEM0_")) delete env[key];
|
|
}
|
|
if (opts.home) env.HOME = opts.home;
|
|
if (opts.env) Object.assign(env, opts.env);
|
|
|
|
try {
|
|
const stdout = execSync(`npx tsx src/index.ts ${args.join(" ")}`, {
|
|
cwd: path.join(__dirname, ".."),
|
|
env,
|
|
encoding: "utf-8",
|
|
timeout: 15000,
|
|
});
|
|
return { stdout, stderr: "", exitCode: 0 };
|
|
} catch (e: any) {
|
|
return {
|
|
stdout: e.stdout ?? "",
|
|
stderr: e.stderr ?? "",
|
|
exitCode: e.status ?? 1,
|
|
};
|
|
}
|
|
}
|
|
|
|
function cleanHome(): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), "mem0-test-"));
|
|
}
|
|
|
|
describe("init flag surface", () => {
|
|
it("init --help lists --agent", () => {
|
|
const result = run(["init", "--help"]);
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.stdout).toContain("--agent");
|
|
});
|
|
|
|
it("init --help describes Agent Mode", () => {
|
|
const result = run(["init", "--help"]);
|
|
expect(result.exitCode).toBe(0);
|
|
// Description must mention what --agent actually does so an agent
|
|
// reading the help can self-discover the bootstrap entrypoint.
|
|
expect(
|
|
result.stdout.includes("Agent Mode") ||
|
|
result.stdout.toLowerCase().includes("unattended"),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("init --help lists --source", () => {
|
|
const result = run(["init", "--help"]);
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.stdout).toContain("--source");
|
|
});
|
|
|
|
it("init --help lists --email and --code", () => {
|
|
const result = run(["init", "--help"]);
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.stdout).toContain("--email");
|
|
expect(result.stdout).toContain("--code");
|
|
});
|
|
});
|
|
|
|
describe("argv preprocessing — --agent reaches init subcommand", () => {
|
|
// Regression for the bug where the global --agent JSON-alias swallowed
|
|
// the init-level --agent flag, making `mem0 init --agent` behave like
|
|
// the plain interactive wizard.
|
|
|
|
it("init --agent triggers bootstrap branch (not the wizard)", () => {
|
|
const home = cleanHome();
|
|
const result = run(["init", "--agent"], {
|
|
home,
|
|
env: {
|
|
MEM0_BASE_URL: "http://127.0.0.1:1", // blackhole
|
|
FORCE_COLOR: "0",
|
|
},
|
|
});
|
|
const combined = (result.stdout + result.stderr).toLowerCase();
|
|
// Either bootstrap-attempt error, or a connection/network error —
|
|
// both prove the --agent path executed (the wizard would prompt for
|
|
// input and succeed/hang, not surface a network error).
|
|
expect(
|
|
combined.includes("agent") ||
|
|
combined.includes("connect") ||
|
|
combined.includes("network") ||
|
|
combined.includes("fetch") ||
|
|
combined.includes("bootstrap"),
|
|
).toBe(true);
|
|
fs.rmSync(home, { recursive: true, force: true });
|
|
});
|
|
});
|
|
|
|
describe("JSON envelope on network failure", () => {
|
|
it("init --agent --json does not leak a stack trace when backend is unreachable", () => {
|
|
const home = cleanHome();
|
|
const result = run(["init", "--agent", "--json"], {
|
|
home,
|
|
env: {
|
|
MEM0_BASE_URL: "http://127.0.0.1:1",
|
|
FORCE_COLOR: "0",
|
|
},
|
|
});
|
|
const combined = result.stdout + result.stderr;
|
|
// No raw Node stack should escape the agent-mode handler.
|
|
expect(combined).not.toMatch(/at \w+\s*\(.+\.ts:\d+/);
|
|
expect(combined).not.toContain("UnhandledPromiseRejection");
|
|
expect(result.exitCode).not.toBe(0);
|
|
fs.rmSync(home, { recursive: true, force: true });
|
|
});
|
|
});
|
|
|
|
describe("top-level help lists init", () => {
|
|
// `mem0 --help` must list `init` so agents walking the top-level help
|
|
// can discover the Agent Mode entrypoint without prior knowledge.
|
|
|
|
it("--help lists init", () => {
|
|
const result = run(["--help"]);
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.stdout).toContain("init");
|
|
});
|
|
});
|